-- LUALOCALS < --------------------------------------------------------- local include, nodecore, pairs, string, table = include, nodecore, pairs, string, table local string_lower = string.lower -- LUALOCALS > --------------------------------------------------------- local modname = minetest.get_current_modname() --nodecore.gametime local cache = {} local global_search_maximum = 361 local directions = { [0] = vector.new(0, 0, -1), vector.new(-1, 0, 0), vector.new(0, 0, 1), vector.new(1, 0, 0), } local down = vector.new(0, -1, 0) local solid_drawtypes = { normal = true, glasslike_framed = true, } --[[ E: air E_: other empty Etype: territory marker of some type Stype: a go stone of some type W: full block wall WE0-WE3: edge concrete WC0-WC3: corner concrete WB: other goban concrete --]] local function _check_position_uncached(pos) local node = minetest.get_node(pos) if node.name == "air" then return "E" end local reg_item = minetest.registered_items[node.name] if not reg_item then return "W" end if reg_item.groups and reg_item.groups.go_stone then return "S" .. reg_item.go_team end if reg_item.groups and reg_item.groups.go_territory_marker then return "E" .. reg_item.go_team end if reg_item.pattern_def then if reg_item.pattern_def.name == "edgy" then return "WE" .. ((node.param2+2)%4) elseif reg_item.pattern_def.name == "corny" then return "WC" .. ((node.param2+2)%4) elseif reg_item.pattern_def.name == "crossy" then return "WB" elseif reg_item.pattern_def.name == "starcrossy" then return "WB" end end if solid_drawtypes[reg_item.drawtype] and not reg_item.groups.falling_node then return "W" else return "E_" end end local function check_position(pos) local hash = minetest.hash_node_position(pos) if cache[hash] then return cache[hash] end local ret = _check_position_uncached(pos) cache[hash] = ret return ret end local function set_and_cache(pos, value) nodecore.set_loud(pos, value) cache[minetest.hash_node_position(pos)] = _check_position_uncached(pos) end local smokecontrol if nodecore.smokeclear then smokecontrol = function(pos, smoke) local existing = check_position(pos) if existing:sub(1, 1) == "S" then local node = minetest.get_node(pos) node.param2 = smoke minetest.swap_node(pos, node) if smoke then nodecore.smokefx(pos, 60, smoke) else nodecore.smokeclear(pos) end else if smoke then nodecore.smokefx(pos, smoke, 1) else nodecore.smokeclear(pos) end end end else smokecontrol = function(pos, smoke) end end -- check for walls, physical or implied local function edge_check(pos, dir, terminate) local under = check_position(pos + down) if under:sub(1, 1) == "W" and under:len() == 3 then local corner = (under:sub(2, 2) == "C") local edge_dir = tonumber(under:sub(3, 3)) if (dir == edge_dir) or (corner and (dir == (edge_dir+1)%4)) then return true end end if not terminate then if edge_check(pos+directions[dir], (dir+2)%4, true) then return true end end return false end --[[ give valid neighbor directions from a position, excluding solid blocks and also respecting borders indicated by goban concrete --]] local function neighbor_dirs(pos) local neighbors = {} for i,v in pairs(directions) do if not edge_check(pos, i) then if check_position(pos + directions[i]):sub(1, 1) ~= "W" then neighbors[#neighbors+1] = i end end end return neighbors end local function connected_search(pos, final_result, early_termination_filter) local group = {pos} local stones = {[minetest.hash_node_position(pos)] = pos} local checked = {} local piece = check_position(pos) local probe = 0 while probe < #group do probe = probe + 1 pos = group[probe] for i,v in pairs(neighbor_dirs(pos)) do local newpos = pos + directions[v] local newhash = minetest.hash_node_position(newpos) if (not stones[newhash]) and (not checked[newhash]) then local newnode = check_position(newpos) if newnode == piece then stones[newhash] = newpos group[#group+1] = newpos else checked[newhash] = true end local filter_result if early_termination_filter then filter_result = early_termination_filter(newnode, #group) end if filter_result then filter_result.stones = stones return filter_result end end end end final_result = final_result(#group) final_result.stones = stones return final_result end local function board_clear_fx(basepos) local group = {basepos} local checked = {[minetest.hash_node_position(basepos)] = basepos} local probe = 0 while probe < #group do probe = probe + 1 local pos = group[probe] for i,v in pairs(neighbor_dirs(pos)) do local newpos = pos + directions[v] if (math.abs(newpos.x - basepos.x) <= 18) and (math.abs(newpos.z - basepos.z) <= 18) then local newhash = minetest.hash_node_position(newpos) local groups = minetest.registered_nodes[minetest.get_node(newpos).name].groups if groups then if groups.ko_stone then set_and_cache(newpos, {name = "air"}) end smokecontrol(newpos) end if (not checked[newhash]) then group[#group+1] = newpos checked[newhash] = true end end end end end local function _check_captures_filter(node, count) if (count > global_search_maximum) or (node:sub(1, 1) == "E") then return {capture = false} end end local function _check_captures_final(count) return {capture = count} end local function check_captures(pos) return connected_search(pos, _check_captures_final, _check_captures_filter) end local function _connected_group_final() return {complete = true} end local function _connected_group_filter(node, count) if (count > global_search_maximum) then return {} end end local function connected_group(pos) local search = connected_search(pos, _connected_group_final, _connected_group_filter) if search.complete then return search.stones else return {pos} end end local function territory_search(pos, max) if check_position(pos) ~= "E" then return {} end local team return connected_search(pos, function() return {team = team} end, function(node, count) if (count > max) then return {} end if node ~= "E_" and ((node:sub(1, 1) == "E") or (node:sub(1, 1) == "S")) and node:len() > 1 then node = node:sub(2, -1) if team then if team ~= node then return {} end else team = node end end end ) end local function multi_eject(proximal, stone, speed, count, inv) local stack_max = minetest.registered_items[stone].stack_max while count > 0 do local items = stone .. " " .. math.min(count, stack_max) if inv then items = inv:add_item("main", items) end nodecore.item_eject(proximal, items, speed) count = count - stack_max end end local firenode = "nc_fire:fire_burst" if not minetest.registered_nodes["nc_fire:fire_burst"] then firenode = "nc_fire:fire" end function lc_liberties.handle_placement(pos) cache = {} --minetest.chat_send_all(tostring(pos)) local our_stone = check_position(pos) if our_stone:sub(1, 1) ~= "S" then return end our_stone = our_stone:sub(2, -1) local captureses = {} local captured = false for i,v in pairs(neighbor_dirs(pos)) do local new_pos = pos + directions[v] local hash = minetest.hash_node_position(new_pos) local checked = false for i2,v2 in pairs(captureses) do if v2.stones[hash] then checked = true end end if not checked then local node = check_position(new_pos) if (node:sub(1, 1) == "S") and (node:sub(2, -1) ~= our_stone) then local captures = check_captures(new_pos) if captures.capture then captured = (captured or 0) + captures.capture end captureses[#captureses+1] = captures end end end local allcaptured = {} local ko if captured then if captured == 1 then for i,v in pairs(neighbor_dirs(pos)) do local new_stone = check_position(pos + directions[v]) if (new_stone:sub(2, -1) == our_stone) or (new_stone:sub(1, 1) == "E") then goto noko end end ko = true ::noko:: end for i,v in pairs(captureses) do if v.capture then local stone = check_position(v.stones[next(v.stones)]):sub(2, -1) -- minetest.get_node().name) local count = 0 local proximal local proximal_c = 0 for i2, v2 in pairs(v.stones) do allcaptured[#allcaptured + 1] = v2 if ko then -- save ko capture position, for adding ko marker in fx phase ko = {stone = stone, pos = v2} end local under = check_position(v2 + down) if (under:sub(1, 1) == "W") and (under:len() > 1) then nodecore.sound_play("nc_fire_ignite", {gain = 1, pos = pos}) set_and_cache(v2, {name = firenode}) else set_and_cache(v2, {name = "air"}) end count = count + 1 if (v2:distance(pos) < 2.3) then proximal_c = proximal_c + 1 if math.random(1, proximal_c) == 1 then proximal = v2 end end end multi_eject(proximal, modname .. ":stone_" .. stone, 3, count) end end else -- if no capture was made, check for a self-capture and reject the move local captures = check_captures(pos) if captures.capture then local stone = minetest.get_node(pos).name set_and_cache(pos, {name = "air"}) nodecore.item_eject(pos, stone, 5) return end end board_clear_fx(pos) smokecontrol(pos, 2+#allcaptured) for i = #allcaptured, 1, -1 do local chosen = math.random(1, i) smokecontrol(allcaptured[chosen], 90) allcaptured[chosen] = allcaptured[i] end if ko then set_and_cache(ko.pos, {name = modname .. ":ko_" .. ko.stone}) end end function lc_liberties.handle_territory_fill(itemstack, placer, pointed_thing) local control = placer.get_player_control and placer:get_player_control() if (control and (control.aux1 or control.sneak)) then local ret = minetest.item_place(itemstack, placer, pointed_thing) if (pointed_thing and pointed_thing.under) then nodecore.node_sound(pointed_thing.under, "place") end return ret end local under if (pointed_thing and pointed_thing.under) then under = minetest.get_node(pointed_thing.under) local override_rightclick = minetest.registered_nodes[under.name].on_rightclick if override_rightclick then return override_rightclick(pointed_thing.under, under, placer, itemstack, pointed_thing) end end if pointed_thing and pointed_thing.above then if under then local reg_item = minetest.registered_items[under.name] if reg_item and reg_item.groups and reg_item.groups.go_stone then set_and_cache(pointed_thing.above, {name = modname .. ":territory_" .. reg_item.go_team}) itemstack:set_count(itemstack:get_count() - 1) return itemstack end end cache = {} local territories = territory_search(pointed_thing.above, itemstack:get_count()) if territories.team then local piece = modname .. ":territory_" .. territories.team local count = 0 for i, v in pairs(territories.stones) do set_and_cache(v, {name = piece}) count = count + 1 end itemstack:set_count(itemstack:get_count() - count) end end return itemstack end function lc_liberties.handle_dig(pos, node, digger) local control = digger.get_player_control and digger:get_player_control() if (control and (control.aux1 or control.sneak)) ~= (minetest.registered_items[node.name].groups.go_territory_marker ~= nil) then cache = {} local stone = minetest.get_node(pos).name local count = 0 for i, v in pairs(connected_group(pos)) do count = count + 1 set_and_cache(v, {name = "air"}) end multi_eject(pos, stone, 1, count, digger and digger:get_inventory()) return true else return minetest.node_dig(pos, node, digger) end end function lc_liberties.handle_click_ko(pos, node, clicker, itemstack, pointed_thing) local handnode = minetest.registered_nodes[itemstack:get_name()] if handnode and handnode.groups.go_stone and handnode.go_team ~= minetest.registered_nodes[node.name].go_team then set_and_cache(pos, {name = handnode.name}) itemstack:set_count(itemstack:get_count() - 1) end return itemstack end