--Prevent standalone execution: if not TRAINSPORTED then print("To prevent players from harm, this file may only be executed by the trAInsported game.") return end --AI by honk: function neighbours(map, x, y) local links = {} if map[x-1][y] == "C" then table.insert(links, {x=x-1, y=y}) end if map[x+1][y] == "C" then table.insert(links, {x=x+1, y=y}) end if map[x][y-1] == "C" then table.insert(links, {x=x, y=y-1}) end if map[x][y+1] == "C" then table.insert(links, {x=x, y=y+1}) end return links end choseDirectionAt = nil BLOCK_WAIT = 3 function ai.blocked(train, possibleDirections, prevDirection) if choseDirectionAt + BLOCK_WAIT > os.time() then --try again at the beginning return prevDirection end --print("blocked for more than "..BLOCK_WAIT.."s: ", train.x ..":".. train.y, prevDirection, train) if prevDirection == "N" then possibleDirs = {"S", "E", "W"} elseif prevDirection == "S" then possibleDirs = {"E", "W", "N"} elseif prevDirection == "E" then possibleDirs = {"W", "S", "N"} else possibleDirs = {"N", "S", "E"} end return possibleDirs[ math.random(#possibleDirs)] end function mapToGraph(map) --printMap(map) local nodes = {} local firstNode = nil for x = 1, map.width do nodes[x] = {} visitedNodes[x] = {} graphNodes[x] = {} for y = 1, map.height do graphNodes[x][y] = {} visitedNodes[x][y] = {} if map[x][y] == "C" then local links = neighbours(map, x,y) local node = {} node.links = links node.x = x node.y = y nodes[x][y] = node if firstNode == nil and #node.links >= 2 then firstNode = node end --endnodes: 1->2 --straight: 1->1 2->2 --point: 1-1,1 2->2,2 end end end --print("left: ", getNumberOfLines()) graph = createGraph(nodes, firstNode, firstNode.links[1]) end graph = nil visitedNodes = {} graphNodes = {} X = 5 Y = 1 --todo split into parts when too large function createGraph(nodes, node, from) if visitedNodes[node.x][node.y][from.x .. from.y] ~= nil then return visitedNodes[node.x][node.y][from.x .. from.y] end local graphNode = {} graphNode.x = node.x graphNode.y = node.y graphNode.link = {} visitedNodes[node.x][node.y][from.x .. from.y] = graphNode for i, link in ipairs(node.links) do if not (link.x == from.x and link.y == from.y and #node.links > 1) then -- can't turn local target = createGraph(nodes, nodes[link.x][link.y], node) table.insert(graphNode.link, target) end end graphNodes[graphNode.x][graphNode.y][from.x ..":".. from.y] = graphNode return graphNode end function printMap(map) str = {} for j = 1, map.height do str[j] = "" for i = 1, map.width do if map[i][j] then str[j] = str[j] .. map[i][j] .. " " else str[j] = str[j] .. "- " end end end for i = 1, #str do print(str[i]) end end originalMap = nil initialBuy = 0 function ai.init(map, money) originalMap = map mapToGraph(map) initialBuy = money/25 --[[while money>=25 do buyTrain(map.width/2,map.height/2) money = money - 25 end--]] end function ai.newTrain(train) trainDelivered = os.clock() end comparisonPosition = {} function ai.chooseDirection(train, possibleDirections) choseDirectionAt = os.clock() if train.passenger ~= nil then if train.passenger.destX ~= train.nextX or train.passenger.destY ~= train.nextY then local route = findRoute(train.nextX, train.nextY, train.x, train.y,train.passenger.destX, train.passenger.destY) --local dir = findRouteOld(train, train.passenger) local dir = getDirection(train.nextX, train.nextY, route[#route].x, route[#route].y) return dir end else comparisonPosition.x = train.nextX comparisonPosition.y = train.nextY table.sort(passengers, comparePassengers) --print("timeleft: ", getNumberOfLines()) local minDist = 9999 for i = 1, math.min(5, #passengers) do if getNumberOfLines() > 4000 then break end local route = findRoute(train.nextX, train.nextY, train.x, train.y, passengers[i].x, passengers[i].y) --print ("route length to:", passengers[i].x..":"..passengers[i].y, #route) if #route < minDist then minDist = #route train.plannedRoute = route train.plannedTarget = passengers[i] end end if train.plannedRoute ~= nil then if #train.plannedRoute == 0 then --todo choose more optimally return end local route = train.plannedRoute local dir = getDirection(train.nextX, train.nextY, route[#route].x, route[#route].y) --print("train: ", train.x..":".. train.y,"->",train.nextX..":"..train.nextY.."\n".. -- " goto:", train.plannedTarget.x..":"..train.plannedTarget.y, "=>", dir) return dir end end end function comparePassengers(a, b) local aVipFactor = 0 local bVipFactor = 0 if buyingTrains then aDiff = os.clock() - a.placed aVipTime = a.vipTime - aDiff aVipFactor = math.max(-3, a.minDistance - aVipTime) bDiff = os.clock() - b.placed bVipTime = b.vipTime - bDiff bVipFactor = math.max(-3, b.minDistance - bVipTime) end --print(aVipFactor) local distanceAX = comparisonPosition.x - a.x local distanceAY = comparisonPosition.y - a.y local distanceBX = comparisonPosition.x - b.x local distanceBY = comparisonPosition.y - b.y return math.sqrt(distanceAX*distanceAX+distanceAY*distanceAY) + a.minDistance/2 + aVipFactor < math.sqrt(distanceBX*distanceBX+distanceBY*distanceBY) + b.minDistance/2 + bVipFactor end routeCache = {} function findRoute(x, y, fromX, fromY, destX, destY) --special case: next cell if (x==destX and y==destY) then local route = {} table.insert(route, {x=destX, y=destY, markFrom={x=x, y=y}}) return route end if routeCache[x..":"..y.."-"..fromX..":"..fromY.."-"..destX..":"..destY] ~= nil then return routeCache[x..":"..y.."-"..fromX..":"..fromY.."-"..destX..":"..destY] end local start = graphNodes[x][y][fromX..":"..fromY] local finish = findRouteDepthFirst(start, destX, destY, nil, nil) local route = {} local node = finish --while node.markFrom.x ~= x or node.markFrom.y ~= y do while node.markFrom ~= start do table.insert(route, node) node = node.markFrom end table.insert(route, node) routeCache[x..":"..y.."-"..fromX..":"..fromY.."-"..destX..":"..destY] = route return route end function getDirection(x, y, nextX, nextY) if x < nextX then return "E" end if x > nextX then return "W" end if y < nextY then return "S" end if y > nextY then return "N" end end findRouteRun = 0 --function findRouteDepthFirst(start, x, y, fromX, fromY) function findRouteDepthFirst(start, x, y) findRouteRun = findRouteRun + 1 local queue = {} table.insert(queue,start) start.mark = findRouteRun while #queue > 0 do node = table.remove(queue, 1) if node.x == x and node.y == y then return node end for i, link in pairs(node.link) do if link.mark ~= findRouteRun then --markedTracks = markedTracks + 1 link.mark = findRouteRun link.markFrom = node table.insert(queue, link) end end end end function getToNode(x, y, dir) local toX = x if dir == "E" then toX = toX + 1 end if dir == "W" then toX = toX - 1 end local toY = y if dir == "N" then toY = toY - 1 end if dir == "S" then toY = toY + 1 end return toX, toY end function getFromNode(x, y, dir) local fromX = x if dir == "E" then fromX = fromX - 1 end if dir == "W" then fromX = fromX + 1 end local fromY = y if dir == "N" then fromY = fromY - 1 end if dir == "S" then fromY = fromY + 1 end return fromX, fromY end DROP_WHEN_FURTHER = 3 function ai.foundPassengers(train, localPassengers) --TODO pick up new passenger, if it's a lot better local toX, toY = getToNode(train.x, train.y, train.dir) local broken = nil local pick = nil for i, localPassenger in pairs(localPassengers) do local found = false for j, passenger in pairs(passengers) do if passenger.name == localPassenger.name then found = true local route = passenger.routeOver[toX..":"..toY] --works as planned if route == nil then print("passenger at", train.x..":"..train.y, "doesn't have a route for:", toX..":"..toY) end if pick == nil or (route ~= nil and #route < #pick.route) then pick = localPassenger localPassenger.route = route localPassenger.index = j end break end end if not found then print("missing passenger entry for: ", localPassenger.name) broken = localPassenger end end if pick == nil and broken ~= nil then return broken end --if pick == nil then -- print("couldn't pick anyone at", train.x, train.y) --end if train.passenger == nil then if pick ~= nil then local test = table.remove(passengers, pick.index) end return pick else local currentDistance = #findRoute(toX, toY, train.x, train.y, train.passenger.destX, train.passenger.destY) if currentDistance > #pick.route + DROP_WHEN_FURTHER then --3 further? repick! print("dropped off: ", train.passenger.name, "at", train.x, train.y,"\n", "saved distance: ", currentDistance-#pick.route) dropPassenger(train) --return pick end end end function ai.foundDestination(train) dropPassenger(train) end trainBought = nil trainDelivered = nil TRAIN_TIMEOUT = 1 buyingTrains = true function ai.enoughMoney(money) if trainBought ~= nil then --print("buying train at: "..os.clock().." last bought: "..trainBought.." last delivered: "..trainDelivered) if trainDelivered == nil and trainBought + TRAIN_TIMEOUT < os.clock() then --no train was ever delivered --print("no train ever") buyingTrains = false return end if trainDelivered ~= nil and trainBought - trainDelivered > TRAIN_TIMEOUT then --last train wasn't delivered within 20 seconds --print("no train within 20s") buyingTrains = false return end end while money >= 25 do buyingTrains = true trainBought = os.clock() buyOptimalTrain() money = money - 25 end end function ai.passengerBoarded(train, passengerName) --TODO remove passenger form list for i,passenger in pairs(passengers) do if passenger.name == passengerName then table.remove(passengers, i) break end end end function randomPrevTrack(x, y) if x == nil or y == nil then return end local neighbours = neighbours(originalMap, x, y) local pick = neighbours[math.random(#neighbours)] local dir = getDirection(pick.x, pick.y, x, y) return pick.x, pick.y, dir end function buyOptimalTrain() if #passengers >= 1 then local pick = passengers[1] for i, passenger in pairs(passengers) do if passenger.minDistance < pick.minDistance then pick = passenger if pick.minDistance < 3 then break end end end --TODO pick direction and start point optimally local minRoute = pick.minRoute local output = "" --print(printRoute(minRoute)) local x, y, dir = pickSpawn(pick) local x, y, dir = pickSpawn(pick) buyTrain(x, y, dir) else buyTrain(originalMap.width/2, originalMap.height/2, randomDirection()) end end function pickSpawn(target) x = target.x y = target.y nextInRouteX = target.minRoute[#target.minRoute].y nextInRouteY = target.minRoute[#target.minRoute].y local neighbours = neighbours(originalMap, x, y) if #neighbours == 1 then --only one place to spawn at a dead end return neighbours[1].x, neighbours[1].y, getDirection(neighbours[1].x, neighbours[1].y, x, y) end local pick = nil for i,neighbour in pairs(neighbours) do if not (neighbour.x == nextInRouteX and neighbour.y == nextInRouteY) then --don't select the way we want to go pick = neighbour break end end return pick.x, pick.y, getDirection(pick.x, pick.y, x, y) end function randomDirection() local dirs = {"N", "E", "S", "W"} return dirs[math.random(#dirs)] end passengers = {} function ai.newPassenger(name, x, y, destX, destY, vipTime) --todo store passengers local passenger = {name=name, x=x, y=y, destX=destX, destY=destY, vipTime=vipTime} passenger.placed = os.clock() passenger.routeOver = {} --print("new passenger: ", x, y, "to", destX, destY) local neighbours = neighbours(originalMap, x, y) local minDist = 9999 for i, nextDir in pairs(neighbours) do local route = findRoute(nextDir.x, nextDir.y, x, y, destX, destY) --print("time left: ", getNumberOfLines(), "\nroute length: ", #route) passenger.routeOver[nextDir.x ..":".. nextDir.y] = route if #route < minDist then minDist = #route minRoute = route end end passenger.minDistance = minDist passenger.minRoute = minRoute table.insert(passengers, passenger) if initialBuy > 0 then if #passengers > 3 then initialBuy = initialBuy -1 buyOptimalTrain() end end end function printGraph(node) if node.mark == "print" then return "" end node.mark = "print" local output = node.x .. ":" .. node.y .. "->" for i, link in pairs(node.link) do output = output .. link.x .. ":" .. link.y .. "," end for i, link in pairs(node.link) do output = output .. " | " output = output .. printGraph(link) end return output end function printRoute(route) local output = "" for i,node in pairs(route) do output = output .. "->" .. node.x..":"..node.y end return output end