From 5a5a136993373ebdd95466c263162776676d8cd3 Mon Sep 17 00:00:00 2001 From: Aklakina Date: Sat, 24 Feb 2024 13:01:10 +0100 Subject: [PATCH] Corrected dijkstra implementation and started implementing the miner logic --- src/MinerCore.lua | 147 ++++++++++----------------- src/common/betterTurtle.lua | 20 ++++ src/common/binaryHeap.lua | 5 +- src/common/coordinate.lua | 121 +++++++++++++++++++++-- src/common/environment.lua | 192 ++++++++++++++++++++++++++---------- tests/src/unitTests.lua | 148 ++++++++++++++++++++++----- 6 files changed, 449 insertions(+), 184 deletions(-) diff --git a/src/MinerCore.lua b/src/MinerCore.lua index 4e05aa6..aeb550f 100644 --- a/src/MinerCore.lua +++ b/src/MinerCore.lua @@ -14,6 +14,21 @@ require('environment') local environment = Environment:new() require('mutex') +minerStates = { + MINING = {}, + FUELING = {}, + SEARCHING = {}, + RETURNING = {} +} + +minerState = minerStates.SEARCHING + +for i=-1, 1 do + for j=-1, 1 do + environment:insertCoordToCheckedBlocks(Coordinate:new(i, 0, j), blockType.BLOCKER) + end +end + function loadConfig() local file = io.open("config.ini", "r") local config = {} @@ -72,10 +87,11 @@ function dumperLoop() end end -function suckLava(direction, blockType, blockPos, nLevel) - if blockType == blockType.FUEL then +function suckLava(direction, _blockType, blockPos) + if _blockType == blockType.FUEL then if betterTurtle.getFuelLevel() > 90000 then environment:storeFuelLocation(blockPos) + minerState = minerStates.SEARCHING return false end Mutex:lock("suckLava") @@ -86,131 +102,72 @@ function suckLava(direction, blockType, blockPos, nLevel) print( "[check]: Refueled using lava source!" ) local lastDirection = betterTurtle.direction betterTurtle:move(direction, true) + environment:insertCoordToCheckedBlocks(betterTurtle.position, blockType.AIR) betterTurtle.select( 1 ) Mutex:unlock("suckLava") - check( nLevel + 1 ) - betterTurtle:move(directions.getInverseDirection(lastDirection), true) + minerState = minerStates.FUELING + return true else print( "[check]: Liquid was not lava!" ) betterTurtle.place() betterTurtle.select( 1 ) Mutex:unlock("suckLava") + minerState = minerStates.SEARCHING + return false end end end end -function mineVein(direction, blockType, nLevel) -- Vein mine function - local lastDirection = betterTurtle.direction - if blockType == blockType.OTHER then +function mineVein(direction, _blockType) + if _blockType == blockType.OTHER then betterTurtle:move(direction, true) - check( nLevel + 1 ) - betterTurtle:move(directions.getInverseDirection(lastDirection), true) + environment:insertCoordToCheckedBlocks(betterTurtle.position, blockType.AIR) + minerState = minerStates.MINING + return true + else + minerState = minerStates.SEARCHING + return false end end local function suckInventory(direction) if betterTurtle:actionInDirection("detect", direction) and betterTurtle:actionInDirection("inspect", direction) then while betterTurtle:actionInDirection("suck", direction) do end + environment:insertCoordToCheckedBlocks(betterTurtle.offsetPosition(direction), blockType.BLOCKER) + minerState = minerStates.SEARCHING + return false end end -function check(nLevel) -- Recursive checker for valuables - if nLevel > 100 then - return - end - local lastDirection = betterTurtle.direction - for k, v in pairs(directions) do - local blockExists, blockData = betterTurtle:actionInDirection("inspect", v) - local blockType = environment:checkBlock(blockData) - if blockExists then - local blockPos = betterTurtle:offsetPosition(v) - if not environment:isBlockChecked(blockPos) then - suckLava(v, blockType, blockPos, nLevel) - mineVein(v, blockType, blockPos, nLevel) - suckInventory(v) - end - end - end - betterTurtle:move(directions.getInverseDirection(lastDirection), true) - betterTurtle:turn(lastDirection) -end - -function branch(mainPos) - local gone = 0 - for i = 1, 25 do - betterTurtle.move("forward", true, true) - print( "[branch]: Dug branch at pos ["..mainPos.."] ["..gone.."]!" ) - gone = gone + 1 - if not ok then break end - check() - if not ok then break end - end - print( "[branch]: Returning to main!" ) - for i = 1, gone do - betterTurtle.move("back", true, true) - end - print( "[branch]: Returned to main!" ) -end - -local function moveUntilWallOrMaxRange(gone, maxRange) - print("[main]: Searching wall") - repeat - if betterTurtle:move("forward", true, true) then - gone = gone + 1 - if gone >= maxRange then - print("[main]: Exceeding 64 blocks range, returning") - ok = false - rangeReached = true - break +function check() + while true do + for k, v in pairs(directions) do + local blockExists, blockData = betterTurtle:actionInDirection("inspect", v) + local blockType = environment:checkBlock(blockData) + if blockExists then + local blockPos = betterTurtle:offsetPosition(v) + if not environment:isBlockChecked(blockPos) then + if suckLava(v, blockType, blockPos, nLevel) then break end + if mineVein(v, blockType, blockPos, nLevel) then break end + if suckInventory(v) then break end + end end end - until betterTurtle.detect() - print("[main]: Found wall") - return gone -end - -local function digMainTunnel(gone, maxRange) - print("Digging main at pos ["..gone.."]") - for i = 1, 3 do - betterTurtle.move("forward", true, true) - print( "[main]: Digging main at pos ["..gone.."]!" ) - if gone >= maxRange then - print("[main]: Exceeding 64 blocks range, returning") - ok = false - rangeReached = true + if minerState == minerStates.SEARCHING then break end - gone = gone + 1 - check() end - return gone + local directions = environment:getClosestMiningPositions(betterTurtle.position) + for _, v in pairs(directions) do + betterTurtle:moveDistance(v) + end end function main() - local gone = 0 while not stop do - local originalDirection = betterTurtle.direction - if rangeReached then - print("[main]: Range reached, turning right") - betterTurtle.relativeTurn("right") - originalDirection = betterTurtle.direction - ok = true - rangeReached = false - end - gone = moveUntilWallOrMaxRange(gone, maxRange) while ok do - gone = digMainTunnel(gone, maxRange) - if not ok then break end - betterTurtle.relativeTurn("right") - print( "[main]: Initiating branch in right wing!" ) - branch(gone-1) - if not ok then break end - betterTurtle.relativeTurn("back") - print( "[main]: Initiating branch in left wing!" ) - branch(gone-1) - if not ok then break end - betterTurtle.relativeTurn("right") + end --not ok, return to base print( "[main]: Returning to base!" ) diff --git a/src/common/betterTurtle.lua b/src/common/betterTurtle.lua index 0a9e2f3..03aefdb 100644 --- a/src/common/betterTurtle.lua +++ b/src/common/betterTurtle.lua @@ -263,4 +263,24 @@ function BetterTurtle:findMaxLevel() end end return maxFuel +end + +function BetterTurtle:moveDistance(distance) + if getmetatable(distance) ~= Distance then + distance = Distance.parse(distance) + end + local _order = {"y", "z", "x"} + for _, axis in ipairs(_order) do + if distance[axis].distance ~= 0 then + local direction = distance[axis].direction + logger:info("Moving in direction " .. direction.name .. " " .. distance[axis].distance .. " times") + self:turn(direction) + local relDirection = directions.convertToForward(direction) + for i = 1, distance[axis].distance do + if not self:move(relDirection, false, true) then + return false + end + end + end + end end \ No newline at end of file diff --git a/src/common/binaryHeap.lua b/src/common/binaryHeap.lua index 521d762..dbba976 100644 --- a/src/common/binaryHeap.lua +++ b/src/common/binaryHeap.lua @@ -52,6 +52,7 @@ function BinaryHeap:insert(coordinate, priority) self.heap[i], self.heap[self:parent(i)] = self.heap[self:parent(i)], self.heap[i] i = self:parent(i) end + self:minHeapify(#self.heap + 1) end function BinaryHeap:minHeapify(i) @@ -78,7 +79,7 @@ function BinaryHeap:pop() self.heap[1] = self.heap[#self.heap] table.remove(self.heap) self:minHeapify(1) - return min.coordinate + return min.coordinate, min.priority end function BinaryHeap:isEmpty() @@ -90,7 +91,7 @@ function BinaryHeap:decreaseKey(coordinate, newPriority) coordinate = Coordinate.parse(coordinate) end for i, v in ipairs(self.heap) do - if v.coordinate == coordinate then + if v.coordinate:isEqual(coordinate) then self.heap[i].priority = newPriority while i > 1 and self.heap[self:parent(i)].priority > self.heap[i].priority do self.heap[i], self.heap[self:parent(i)] = self.heap[self:parent(i)], self.heap[i] diff --git a/src/common/coordinate.lua b/src/common/coordinate.lua index 1ccc969..0a37e49 100644 --- a/src/common/coordinate.lua +++ b/src/common/coordinate.lua @@ -155,7 +155,18 @@ function Coordinate:__tostring() return "["..self.x..", "..self.y..", "..self.z.."]" end +function Coordinate:isEqual(o) + if not (getmetatable(o) == Coordinate) then + o = Coordinate.parse(o) + end + if o == nil then + return false + end + return self.x == o.x and self.y == o.y and self.z == o.z +end + function Coordinate.__eq(o, t) + error("DEPRECATED") logger:debug("Comparing " .. tostring(o) .. " and " .. tostring(t)) if getmetatable(t) ~= Coordinate then t = Coordinate.parse(t) @@ -171,6 +182,16 @@ function Coordinate.__eq(o, t) return o.x == t.x and o.y == t.y and o.z == t.z end +function Coordinate:isSimpleMovement(o) + if not (getmetatable(o) == Coordinate) then + o = Coordinate.parse(o) + end + if o == nil then + return false + end + return (self.x == o.x and self.y == o.y) or (self.x == o.x and self.z == o.z) or (self.y == o.y and self.z == o.z) +end + Distance = { x = {direction = directions.right, distance = 0}, y = {direction = directions.up, distance = 0}, @@ -178,18 +199,37 @@ Distance = { distance = Coordinate:new(0, 0, 0, directions.any) } -function Distance:new(t, o) - if not (getmetatable(t) == Coordinate) then - t = Coordinate.parse(t) +function Distance.parse(o) + if getmetatable(o) == Distance then + return o + elseif type(o) == "table" then + if o.x and o.y and o.z then + return Distance:new({0, 0, 0}, {o.x, o.y, o.z}) + elseif type(o[1]) == "number" and type(o[2]) == "number" and type(o[3]) == "number" then + return Distance:new({0, 0, 0}, {o[1], o[2], o[3]}) + else + error("Invalid arguments for Distance.parse") + end + elseif o == nil then + return nil + else + logger:error("Invalid arguments for Distance.parse, got: " .. type(o)) + error("Invalid arguments for Distance.parse") end - if not (getmetatable(o) == Coordinate) then - o = Coordinate.parseInto(o) +end + +function Distance:new(origin, target) + if not (getmetatable(origin) == Coordinate) then + origin = Coordinate.parse(origin) + end + if not (getmetatable(target) == Coordinate) then + target = Coordinate.parse(target) end local _ret = { - x = {direction = o.x - t.x < 0 and directions.left or directions.right, distance = o.x - t.x}, - y = {direction = o.y - t.y < 0 and directions.down or directions.up, distance = o.y - t.y}, - z = {direction = o.z - t.z < 0 and directions.back or directions.forward, distance = o.z - t.z}, - distance = Coordinate:new(o.x - t.x, o.y - t.y, o.z - t.z, directions.any) + x = { direction = target.x - origin.x < 0 and directions.left or directions.right, distance = math.abs(target.x - origin.x)}, + y = { direction = target.y - origin.y < 0 and directions.down or directions.up, distance = math.abs(target.y - origin.y)}, + z = { direction = target.z - origin.z < 0 and directions.back or directions.forward, distance = math.abs(target.z - origin.z)}, + distance = Coordinate:new(target.x - origin.x, target.y - origin.y, target.z - origin.z, directions.any) } setmetatable(_ret, self) self.__index = self @@ -199,3 +239,66 @@ end function Distance:getAbsolute() return math.sqrt(self.distance * self.distance) end + +function Distance:isEqual(o) + if not (getmetatable(o) == Distance) then + o = Distance.parse(o) + end + if o == nil then + return false + end + return self.distance:isEqual(o.distance) +end + +function Distance:__tostring() + return "[".. self.x.direction.name.. ": " ..self.x.distance..", "..self.y.direction.name.. ": " ..self.y.distance..", "..self.z.direction.name.. ": " ..self.z.distance.."]" +end + +function Distance.merge(o, t) + if not (getmetatable(o) == Distance) then + o = Distance.parse(o) + end + if not (getmetatable(t) == Distance) then + t = Distance.parse(t) + end + if o == nil or t == nil then + return o and o or t + end + return Distance:new({0, 0, 0}, o.distance + t.distance) +end + +function Distance.checkIfStraight(o, t) + if not (getmetatable(o) == Distance) then + o = Distance.parse(o) + end + if not (getmetatable(t) == Distance) then + t = Distance.parse(t) + end + if o == nil or t == nil then + return false + end + -- If the difference in the two distances is only on 1 axis and the other two are 0, then the two distances are straight + return o.distance:isSimpleMovement(t.distance) +end + +function Distance.fromPath(path) + logger:debug("Merging straight movements") + local distances = {} + -- Every consecutive coordinate change on the same axis can be merged into one distance vector + table.insert(distances, Distance:new(path[1], path[2])) + for i = 3, #path do + local dist = Distance:new(path[i-1], path[i]) + if not Distance.checkIfStraight(distances[#distances], dist) then + table.insert(distances, dist) + logger:debug("Movement: ".. tostring(path[i-1]) .. " to " .. tostring(path[i]) .. " is not straight from ".. tostring(path[i-2]) .. " to " .. tostring(path[i-1]) .. ". Adding new distance vector") + else + distances[#distances] = Distance.merge(distances[#distances], dist) + logger:debug("Movement: ".. tostring(path[i-1]) .. " to " .. tostring(path[i]) .. " is straight from ".. tostring(path[i-2]) .. " to " .. tostring(path[i-1]) .. ". Merging distance vectors") + end + end + logger:debug("Merged distances: ") + for i, v in ipairs(distances) do + logger:debug(tostring(v)) + end + return distances +end \ No newline at end of file diff --git a/src/common/environment.lua b/src/common/environment.lua index 74843cc..a2ee985 100644 --- a/src/common/environment.lua +++ b/src/common/environment.lua @@ -10,13 +10,16 @@ require('coordinate') local logger = Logger:new(Logger.levels.DEBUG, "Environment") +require('binaryHeap') + blockType = { WASTE = {}, FUEL = {}, OTHER = {}, BLOCKER = {}, AIR = {}, - UNKNOWN = {} + UNKNOWN = {}, + PATH = {} } Environment = { @@ -24,16 +27,21 @@ Environment = { wasteBlocks = {}, blockers = {}, fuels = {}, - fuelLocations = {} + fuelLocations = {}, + miningMap = {}, + checkQueue = BinaryHeap:new() } function Environment:getBlockAtPosition(coordinate) if getmetatable(coordinate) ~= Coordinate then coordinate = Coordinate.parse(coordinate) end + local path = self.miningMap[tostring(coordinate)] if self.checkedBlocks[coordinate.y] and self.checkedBlocks[coordinate.y][coordinate.x] and self.checkedBlocks[coordinate.y][coordinate.x][coordinate.z] then local blockType = self.checkedBlocks[coordinate.y][coordinate.x][coordinate.z] return blockType + elseif path then + return blockType.PATH else return blockType.UNKNOWN end @@ -91,8 +99,31 @@ local function loadBlockers() logger:debug("Finished loading blockers from file") end +local function generateMiningMap() + -- The mining map should be a set of static coordinates at which the robot should check for valuables. + -- For the mining operation to be efficient it is advised to use branch mining. + -- for example: + -- x||x||x||x||x + -- xxxxxxxxxxxxx + -- x||x||x||x||x + -- Where x marks the coordinates at which the robot should check for valuables. + -- and | marks the coordinates that should not be present in the mining map + -- The robot should start at the first x and move to the next x in the same row. + for x = -255, 255, 1 do + for z = -255, 255, 1 do + if x == 0 or z == 0 then + Environment.miningMap[tostring(Coordinate:new(x, 0, z))] = blockType.UNKNOWN + elseif x % 3 == 0 or z % 3 == 0 then + Environment.miningMap[tostring(Coordinate:new(x, 0, z))] = blockType.UNKNOWN + end + end + end +end + loadWasteBlocks() loadFuels() +loadBlockers() +generateMiningMap() function Environment:new() local o = {} @@ -137,6 +168,8 @@ function Environment:insertCoordToCheckedBlocks(coordinate, blockType) logger:debug("Finished inserting coordinate " .. tostring(coordinate) .. " to checked blocks") end +Environment:insertCoordToCheckedBlocks(Coordinate:new(0, 0, 0), blockType.AIR) + function Environment:checkBlockType(block_type) if not block_type then return blockType.AIR @@ -184,100 +217,155 @@ function Environment:storeFuelLocation(coordinate) logger:debug("Finished storing fuel location at coordinate " .. tostring(coordinate)) end -function Environment:dijkstra(source, target) +function Environment:addPositionToCheckQueue(coordinate) + if getmetatable(coordinate) ~= Coordinate then + coordinate = Coordinate.parse(coordinate) + end + local blockType = self:getBlockAtPosition(coordinate) + local priority = self:getCost(blockType) + logger:debug("Adding position " .. tostring(coordinate) .. " to check queue with priority " .. tostring(priority)) + self.checkQueue:insert(coordinate, priority) +end + +function Environment:getClosestMiningPositions(position) + if getmetatable(position) ~= Coordinate then + position = Coordinate.parse(position) + end + logger:debug("Getting closest mining positions to " .. tostring(position)) + local coordinates = {} + local heapObjects = {} + if self.checkQueue:isEmpty() then + -- It is not needed to get every position from the mining map. + for x = -2, 2, 1 do + for z = -2, 2, 1 do + local coordinate = Coordinate:new(position.x + x, 0, position.z + z) + if self.miningMap[tostring(coordinate)] then + table.insert(coordinates, coordinate) + end + end + end + else + for i = 1, 5 do + table.insert(heapObjects, self.checkQueue:pop()) + table.insert(coordinates, heapObjects[i].coordinate) + end + end + local path, target = self:dijkstra(position, coordinates) + for i, v in heapObjects do + if not v.coordinate:isEqual(target) then + self.checkQueue:insert(v.coordinate, v.priority - 1) + end + end +end + +function Environment:dijkstra(source, targets) if getmetatable(source) ~= Coordinate then source = Coordinate.parse(source) end - if getmetatable(target) ~= Coordinate then - target = Coordinate.parse(target) + if getmetatable(targets) ~= Coordinate then + if pcall(Coordinate.parse(targets)) then + targets = {Coordinate.parse(targets)} + else + for i, target in ipairs(targets) do + if getmetatable(target) ~= Coordinate then + targets[i] = Coordinate.parse(target) + end + end + end + else + targets = {targets} end - logger:debug("Starting Dijkstra algorithm from " .. tostring(source) .. " to " .. tostring(target)) + logger:debug("Starting Dijkstra algorithm from " .. tostring(source) .. " to multiple targets") local distance = {} local previous = {} local queue = BinaryHeap:new() - local visited = {} for y, row in pairs(self.checkedBlocks) do for x, column in pairs(row) do for z, blockType in pairs(column) do local coordinate = Coordinate:new(x, y, z) - if coordinate == source then - distance[coordinate] = 0 + if source:isEqual(coordinate) then + distance[tostring(coordinate)] = 0 + queue:insert(coordinate, 0) else - distance[coordinate] = math.huge + distance[tostring(coordinate)] = math.huge + queue:insert(coordinate, math.huge + self:heuristic(coordinate, targets)) -- heuristic from the first target end - queue:insert(coordinate, self:heuristic(coordinate, source) + self:heuristic(coordinate, target)) end end end - distance[source] = 0 - queue:insert(source, 0) - --queue:insert(source, distance[source] + self:heuristic(source, target)) - --queue:insert(target, self:heuristic(source, target)) - logger:debug(tostring(queue)) - -- sleep for 10 second - -- os.sleep(10) + + local targetReached = nil while not queue:isEmpty() do local current = queue:pop() - if not visited[current] then - visited[current] = true - - if current == target then + for _, target in ipairs(targets) do + if current:isEqual(target) then + targetReached = target break end - for direction, data in pairs(self:getNeighbours(current)) do - local alt = distance[current] + self:getCost(data.type) - logger:debug("New calculated distance for " .. tostring(data.position) .. " is " .. tostring(alt)) - if distance[data.position] then - --distance[data.position] = math.huge - --queue:insert(data.position, 0) - - if alt < distance[data.position] then - logger:debug("Updating distance for " .. tostring(data.position) .. " to " .. tostring(alt)) - distance[data.position] = alt - previous[data.position] = current - queue:decreaseKey(data.position, alt) - end - end + end + if targetReached then + break + end + for direction, data in pairs(self:getNeighbours(current)) do + local alt = distance[tostring(current)] + self:getCost(data.type) + if not distance[tostring(data.position)] then + distance[tostring(data.position)] = math.huge + queue:insert(data.position, math.huge + self:heuristic(data.position, targets[1])) -- heuristic from the first target + end + if alt < distance[tostring(data.position)] then + distance[tostring(data.position)] = alt + previous[tostring(data.position)] = current + queue:decreaseKey(data.position, alt) end end end local path = {} - local u = target - if previous[u] or u == source then + local u = targetReached + local cost = distance[tostring(u)] + if previous[tostring(u)] or source:isEqual(u) then while u do - table.insert(path, 1, Distance:new(previous[u], u)) - u = previous[u] + table.insert(path, 1, u) + u = previous[tostring(u)] end end - - -- Add logging here - print("Path length: " .. #path) - for i, distance in ipairs(path) do - print("Step " .. i .. ": from " .. tostring(distance.from) .. " to " .. tostring(distance.to)) + logger:debug("Finished Dijkstra algorithm from " .. tostring(source) .. " to multiple targets") + logger:debug("Path length: " .. #path) + for i, v in pairs(path) do + logger:debug("Path[" .. i .. "]: " .. tostring(v)) end + logger:debug("Cost: " .. tostring(cost)) - return path + return Distance.fromPath(path), targetReached, cost end function Environment:getCost(_blockType) if not _blockType then - _blockType = blockType.AIR + _blockType = blockType.UNKNOWN end if _blockType == blockType.FUEL or _blockType == blockType.AIR then return 1 elseif _blockType == blockType.WASTE or _blockType == blockType.OTHER then return 10 + elseif _blockType == blockType.PATH then + return 5 else return 1000 end end -- Add a heuristic function to estimate the cost from the current node to the target -function Environment:heuristic(current, target) - local dx = math.abs(current.x - target.x) - local dy = math.abs(current.y - target.y) - local dz = math.abs(current.z - target.z) - return dx + dy + dz -- Use Manhattan distance as the heuristic +function Environment:heuristic(current, targets) + local minDistance = math.huge + for _, target in ipairs(targets) do + local dx = math.abs(current.x - target.x) + local dy = math.abs(current.y - target.y) + local dz = math.abs(current.z - target.z) + local distance = dx + dy + dz -- Manhattan distance + if distance < minDistance then + minDistance = distance + end + end + return minDistance end \ No newline at end of file diff --git a/tests/src/unitTests.lua b/tests/src/unitTests.lua index fa3f862..b21835c 100644 --- a/tests/src/unitTests.lua +++ b/tests/src/unitTests.lua @@ -38,7 +38,7 @@ end function TestCoordinate:testEquality() local coordinate1 = Coordinate:new(1, 2, 3, directions.any) local coordinate2 = Coordinate:new(1, 2, 3, directions.forward) - lu.assertEquals(coordinate1, coordinate2) + lu.assertEvalToTrue(coordinate1:isEqual(coordinate2)) end TestDirection = {} @@ -66,9 +66,9 @@ function TestDistance:testNewDistance() local coordinate1 = Coordinate:new(1, 2, 3, directions.forward) local coordinate2 = Coordinate:new(4, 5, 6, directions.right) local distance = Distance:new(coordinate2, coordinate1) - lu.assertEquals(distance.x.distance, -3) - lu.assertEquals(distance.y.distance, -3) - lu.assertEquals(distance.z.distance, -3) + lu.assertEquals(distance.x.distance, 3) + lu.assertEquals(distance.y.distance, 3) + lu.assertEquals(distance.z.distance, 3) end function TestDistance:testGetDistance() @@ -78,6 +78,67 @@ function TestDistance:testGetDistance() lu.assertEquals(distance:getAbsolute(), math.sqrt(27)) end +function TestDistance:isEqualWithEqualDistances() + local distance1 = Distance:new(Coordinate:new(1, 2, 3), Coordinate:new(4, 5, 6)) + local distance2 = Distance:new(Coordinate:new(1, 2, 3), Coordinate:new(4, 5, 6)) + lu.assertTrue(distance1:isEqual(distance2)) +end + +function TestDistance:isEqualWithUnequalDistances() + local distance1 = Distance:new(Coordinate:new(1, 2, 3), Coordinate:new(4, 5, 6)) + local distance2 = Distance:new(Coordinate:new(1, 2, 3), Coordinate:new(5, 6, 7)) + lu.assertFalse(distance1:isEqual(distance2)) +end + +function TestDistance:isEqualWithNonDistanceObject() + local distance = Distance:new(Coordinate:new(1, 2, 3), Coordinate:new(4, 5, 6)) + lu.assertFalse(distance:isEqual({x = 3, y = 3, z = 3})) +end + +function TestDistance:fromPathWithStraightPath() + local path = { + Coordinate:new(1, 1, 1), + Coordinate:new(2, 1, 1), + Coordinate:new(3, 1, 1), + Coordinate:new(4, 1, 1) + } + local distances = Distance.fromPath(path) + lu.assertEquals(#distances, 3) + lu.assertEquals(distances[1].x.distance, 1) + lu.assertEquals(distances[2].x.distance, 1) + lu.assertEquals(distances[3].x.distance, 1) +end + +function TestDistance:fromPathWithZigZagPath() + local path = { + Coordinate:new(1, 1, 1), + Coordinate:new(2, 1, 1), + Coordinate:new(2, 1, 2), + Coordinate:new(3, 1, 2), + Coordinate:new(3, 1, 3) + } + local distances = Distance.fromPath(path) + lu.assertEquals(#distances, 4) + lu.assertEquals(distances[1].x.distance, 1) + lu.assertEquals(distances[2].z.distance, 1) + lu.assertEquals(distances[3].x.distance, 1) + lu.assertEquals(distances[4].z.distance, 1) +end + +function TestDistance:fromPathWithEmptyPath() + local path = {} + local distances = Distance.fromPath(path) + lu.assertEquals(#distances, 0) +end + +function TestDistance:fromPathWithSinglePointPath() + local path = { + Coordinate:new(1, 1, 1) + } + local distances = Distance.fromPath(path) + lu.assertEquals(#distances, 0) +end + require('binaryHeap') TestBinaryHeap = {} @@ -121,6 +182,42 @@ function TestBinaryHeap:testIsEmpty() lu.assertFalse(self.binaryHeap:isEmpty()) end +function TestBinaryHeap:testInsertWithExistingPriority() + self.binaryHeap:insert(Coordinate:new(1, 2, 3), 1) + self.binaryHeap:insert(Coordinate:new(4, 5, 6), 1) + lu.assertEquals(self.binaryHeap.heap[1].coordinate.x, 1) + lu.assertEquals(self.binaryHeap.heap[1].coordinate.y, 2) + lu.assertEquals(self.binaryHeap.heap[1].coordinate.z, 3) + lu.assertEquals(self.binaryHeap.heap[1].priority, 1) + lu.assertEquals(self.binaryHeap.heap[2].coordinate.x, 4) + lu.assertEquals(self.binaryHeap.heap[2].coordinate.y, 5) + lu.assertEquals(self.binaryHeap.heap[2].coordinate.z, 6) + lu.assertEquals(self.binaryHeap.heap[2].priority, 1) +end + +function TestBinaryHeap:testPopWithEmptyHeap() + local coordinate = self.binaryHeap:pop() + lu.assertEquals(coordinate, nil) +end + +function TestBinaryHeap:testDecreaseKeyWithNonExistingCoordinate() + local coordinate = Coordinate:new(7, 8, 9) + self.binaryHeap:decreaseKey(coordinate, 1) + lu.assertEquals(#self.binaryHeap.heap, 0) +end + +function TestBinaryHeap:testDecreaseKeyWithExistingCoordinate() + local coordinate = Coordinate:new(1, 2, 3) + self.binaryHeap:insert(coordinate, 2) + self.binaryHeap:decreaseKey(coordinate, 1) + lu.assertEquals(self.binaryHeap.heap[1].priority, 1) +end + +function TestBinaryHeap:testIsEmptyWithNonEmptyHeap() + self.binaryHeap:insert(Coordinate:new(1, 2, 3), 1) + lu.assertFalse(self.binaryHeap:isEmpty()) +end + require('environment') TestEnvironment = {} @@ -366,34 +463,33 @@ function TestEnvironment:testDijkstraWithPopulatedCheckedBlocks() local source = Coordinate:new(1, 2, 3) local target = Coordinate:new(4, 5, 6) for y = 0, 6 do - environment.checkedBlocks[y] = {} for x = 0, 6 do - environment.checkedBlocks[y][x] = {} for z = 0, 6 do - environment.checkedBlocks[y][x][z] = blockType.WASTE + environment:insertCoordToCheckedBlocks({x, y, z}, blockType.WASTE) end end end - local path = environment:dijkstra(source, target) + -- The source and the targe should be AIR as well as a well defined path that should be asserted in the path + environment:insertCoordToCheckedBlocks({1, 2, 3}, blockType.AIR) + environment:insertCoordToCheckedBlocks({4, 5, 6}, blockType.AIR) + + -- The path + environment:insertCoordToCheckedBlocks({1, 2, 4}, blockType.AIR) + environment:insertCoordToCheckedBlocks({1, 2, 5}, blockType.AIR) + environment:insertCoordToCheckedBlocks({1, 2, 6}, blockType.AIR) + environment:insertCoordToCheckedBlocks({2, 2, 6}, blockType.AIR) + environment:insertCoordToCheckedBlocks({3, 2, 6}, blockType.AIR) + environment:insertCoordToCheckedBlocks({4, 2, 6}, blockType.AIR) + environment:insertCoordToCheckedBlocks({4, 3, 6}, blockType.AIR) + environment:insertCoordToCheckedBlocks({4, 4, 6}, blockType.AIR) + environment:insertCoordToCheckedBlocks({4, 5, 6}, blockType.AIR) + + local path, cost = environment:dijkstra(source, target) + lu.assertEquals(#path, 3) - lu.assertEquals(path[1].from.x, 1) - lu.assertEquals(path[1].from.y, 2) - lu.assertEquals(path[1].from.z, 3) - lu.assertEquals(path[1].to.x, 2) - lu.assertEquals(path[1].to.y, 2) - lu.assertEquals(path[1].to.z, 3) - lu.assertEquals(path[2].from.x, 2) - lu.assertEquals(path[2].from.y, 2) - lu.assertEquals(path[2].from.z, 3) - lu.assertEquals(path[2].to.x, 3) - lu.assertEquals(path[2].to.y, 2) - lu.assertEquals(path[2].to.z, 3) - lu.assertEquals(path[3].from.x, 3) - lu.assertEquals(path[3].from.y, 2) - lu.assertEquals(path[3].from.z, 3) - lu.assertEquals(path[3].to.x, 4) - lu.assertEquals(path[3].to.y, 2) - lu.assertEquals(path[3].to.z, 3) + lu.assertEvalToTrue(path[1]:isEqual({0, 0, 3})) + lu.assertEvalToTrue(path[2]:isEqual({3, 0, 0})) + lu.assertEvalToTrue(path[3]:isEqual({0, 3, 0})) end os.exit(lu.LuaUnit.run()) \ No newline at end of file