--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 Automate: directions = {N=0,E=1,S=2,W=3} function ai.init(map, money, maximumTrains) trainsleft = maximumTrains numberOfTrains = 0 pauseOnError (false) passengerMap = {}; buyableTrains = money / 25 vertices = {} verticesList = {} for x=1, map.width do vertices [x] = {} passengerMap [x] = {} for y=1, map.height do vertices [x][y] = {} vertices[x][y]["N"] = {x=x, y=y, dir = "N"} vertices[x][y]["E"] = {x=x, y=y, dir = "E"} vertices[x][y]["S"] = {x=x, y=y, dir = "S"} vertices[x][y]["W"] = {x=x, y=y, dir = "W"} passengerMap [x][y] = {} end end for y=1, map.height do for x=1, map.width do if map[x][y] == "C" then local inVertices, outVertices = {}, {} local numNeighbours = 0; if map[x-1][y] == "C" then inVertices["W"] = vertices[x-1][y]["E"] outVertices["W"] = vertices[x][y]["W"] numNeighbours = numNeighbours + 1 end if map[x+1][y] == "C" then inVertices["E"] = vertices[x+1][y]["W"] outVertices["E"] = vertices[x][y]["E"] numNeighbours = numNeighbours + 1 end if map[x][y-1] == "C" then inVertices["N"] = vertices[x][y-1]["S"] outVertices["N"] = vertices[x][y]["N"] numNeighbours = numNeighbours + 1 end if map[x][y+1] == "C" then inVertices["S"] = vertices[x][y+1]["N"] outVertices["S"] = vertices[x][y]["S"] numNeighbours = numNeighbours + 1 end for inD, sourceVertice in pairs(inVertices) do local neighbours = {} sourceVertice.neighbour = neighbours table.insert(verticesList,sourceVertice) for outD, i in pairs(directions) do local targetVertice = outVertices[outD] if targetVertice then if inD ~= outD or numNeighbours == 1 then table.insert (neighbours, targetVertice) end if targetVertice.cost then targetVertice.cost = targetVertice.cost + .1 else if numNeighbours == 1 then targetVertice.cost = 3.4 elseif (directions[inD] + directions[outD]) % 2 == 0 then targetVertice.cost = 1.4 +(numNeighbours-2)*.1 elseif (directions[inD] + directions[outD]) % 4 == 1 then targetVertice.cost = 1.5 +(numNeighbours-1)*.1 else if numNeighbours == 4 then targetVertice.cost = 1.0 else targetVertice.cost = 0.9 end end end else vertices[x][y][outD] = nil end end end end end end numberOfVertices = #verticesList for k, sourceVertice in pairs(verticesList) do prepareForDijkstra (sourceVertice) end for k, sourceVertice in pairs(verticesList) do dijkstra (sourceVertice) end for k, sourceVertice in pairs (verticesList) do local nav = {} sourceVertice.navigation = nav for targetVertice, i in pairs (sourceVertice.prev) do nav [targetVertice] = navigate (sourceVertice, targetVertice) end end end function getDistance (startVertice, endVertice) if (not startVertice.prev) then dijkstra (startVertice, endVertice) elseif startVertice.Qo [endVertice] or startVertice.Q [endVertice] then dijkstra (startVertice, endVertice) end local dist = startVertice.dist [endVertice] return dist end function prepareForDijkstra (sourceVertice) local Q = {} local dist = {} local Qo = {} for j, targetVertice in pairs (verticesList) do if sourceVertice == targetVertice then Q [targetVertice] = 0 dist [targetVertice] = 0 else Qo [targetVertice] = numberOfVertices dist [targetVertice] = numberOfVertices end end sourceVertice.Q = Q sourceVertice.Qo = Qo sourceVertice.dist = dist sourceVertice.prev = {} end function dijkstra (sourceVertice, endVertice) local prev = sourceVertice.prev if prev == nil then prepareForDijkstra (sourceVertice) prev = sourceVertice.prev end local dist = sourceVertice.dist local Q = sourceVertice.Q local n = 0 local numLine = 0 while true do n = n+1 local u local minPriority = math.huge for e, prio in pairs (Q) do if prio < minPriority then minPriority = prio u = e end end if u == nil or u == endVertice then break end Q [u] = nil for j, v in pairs (u.neighbour) do local alt = dist [u] + v.cost local distV = dist [v] if alt == nil or distV == nil then end if alt < distV then dist [v] = alt prev [v] = u if distV == numberOfVertices then sourceVertice.Qo [v] = nil end Q [v] = alt end end end end function nearestEndVertice (startVertice, x2, y2) local minDist = numberOfVertices local nearestVertice for d, i in pairs(directions) do local endVertice = vertices[x2][y2][d] if endVertice then local newDist = getDistance (startVertice, endVertice) if newDist < minDist then minDist = newDist nearestVertice = endVertice end end end return nearestVertice, minDist end function nearestVertices (x1, y1, x2, y2) local minDist = numberOfVertices local nearestStartVertice, nearestEndVertice local n=0 local initialLines = getNumberOfLines() for e, startVertice in pairs(vertices[x1][y1]) do for d, endVertice in pairs(vertices[x2][y2]) do local newDist = getDistance (startVertice, endVertice) if newDist < minDist then minDist = newDist nearestStartVertice = startVertice nearestEndVertice = endVertice end end n = n+1 local numLine, totLine = getNumberOfLines() if numLine*(n+2)/n > totLine-initialLines then break end end return nearestStartVertice, nearestEndVertice, minDist end passengers = {} numberOfPassengers = 0 function ai.newPassenger(name, x, y, destX, destY, vipTime) numberOfPassengers = numberOfPassengers+1 local p = {} passengers [name] = p p.name = name p.x = x p.y = y p.destX = destX p.destY = destY if vipTime then p.vipUntil = mapTime() + vipTime end local pasOnTile = passengerMap[x][y] pasOnTile [name] = p local vertice = nearestVertices (x, y, destX, destY) if buyableTrains >= 1 and trainsleft > 0 then buyNewTrain (vertice) end end function ai.foundPassengers (train, passengerList) local startVertice = vertices [train.x] [train.y] [train.dir] local a,b,dist if train.passenger then a,b,dist = getBestPassengerWhenFoundPassenger ({train.passenger}, startVertice, true) end local pas, endVertice, minDist = getBestPassengerWhenFoundPassenger (passengerList, startVertice) if train.passenger then if minDist +2.7 > dist then return else train.passenger.reservedForTrain = nil dropPassenger (train) end end local t = trains[train.ID] t.nav = navigate (startVertice, endVertice) local res = t.reservedForPassenger if (res) then t.reservedForPassenger = nil res.reservedForTrain = nil end local p = passengers[pas.name] local resTrain = p.reservedForTrain if resTrain and resTrain ~= t then resTrain.nav = {} p.reservedForTrain = nil end p.train = t passengerMap[train.x][train.y][p.name] = nil numberOfPassengers = numberOfPassengers-1 return pas end function putReservation (train, passenger) end function removeReservation (train, passenger) end function ai.passengerBoarded (train, passenger) passengerMap[train.x][train.y][passenger.name] = nil numberOfPassengers = numberOfPassengers-1 local pas = passengers[passenger] passengers [passenger] = nil local resTrain = pas.reservedForTrain if resTrain then resTrain.nav = {} resTrain.reservedForPassenger = nil end end function ai.foundDestination (train) dropPassenger (train) passengers [train.passenger.name] = nil end function navigate (startVertice, targetVertice) local nav = {} local u = targetVertice local prev = startVertice.prev local p while u do local neighbours = u.neighbour if p and p ~= targetVertice and #neighbours > 1 then table.insert (nav, p.dir) end p = u u = prev [u] end return nav end function getPassengerPriority (pas, dist) if pas.vipUntil and trainsleft*25 > 5*numberOfTrains and pas.vipUntil > mapTime() + (dist+3)*1.2 then dist = dist/3 end for k, e in pairs (passengerMap[pas.destX][pas.destY]) do dist = dist - numberOfVertices / numberOfPassengers break end return dist end function getBestPassengerWhenFoundPassenger (passengerList, currentVertice, current) local minDist = math.huge local bestPassenger, targetVertice local n = 0 local initialLines = getNumberOfLines() for i, pas in pairs (passengerList) do pas = passengers[pas.name] if current or (not pas.train or pas.reservedForTrain) then for d, endVertice in pairs(vertices[pas.destX][pas.destY]) do local dist = getDistance (currentVertice, endVertice) dist = getPassengerPriority (pas, dist) if dist < minDist then minDist = dist bestPassenger = pas targetVertice = endVertice end end n=n+1 local numLine, totLine = getNumberOfLines () if numLine*(n+2)/n > totLine-initialLines then break end end end return bestPassenger, targetVertice, minDist end function ai.chooseDirection (train, possibleDirections) local t = trains [train.ID] local nav = t.nav if nav then local dir = table.remove (nav) if dir then return dir end end local startVertice = vertices[train.x][train.y][train.dir] local endVertice, bestEndVertice local passenger local minDist = math.huge local bestPickupVertice local breaked = false local n = 0 local maxIterationTime = 0 local lastIteration = getNumberOfLines() for i, pas in pairs (passengers) do if not pas.train or pas.reservedForTrain then for d, pickupVertice in pairs(vertices[pas.x][pas.y]) do local pickupDist = getDistance (startVertice, pickupVertice) for d, endVertice in pairs(vertices[pas.destX][pas.destY]) do local deliveryDist = getDistance (pickupVertice, endVertice) local dist = pickupDist + deliveryDist dist = getPassengerPriority (pas, dist) if dist < minDist then minDist = dist passenger = pas bestPickupVertice = pickupVertice bestEndVertice = endVertice end end n = n+1 local numLine, totLine = getNumberOfLines() local diffLine = numLine - lastIteration if diffLine > maxIterationTime then maxIterationTime = diffLine end if numLine+2*maxIterationTime > totLine then breaked = true break end end end if breaked then break end end endVertice = bestPickupVertice passenger.reservedForTrain = t t.reservedForPassenger = passenger nav = navigate (startVertice, endVertice) t.nav = nav local dir = table.remove (nav) if dir then return dir end nav = navigate (startVertice, bestEndVertice) t.nav = nav dir = table.remove (nav) if dir then return dir end end function buyNewTrain (vertice) if trainsleft == 0 then return end buyTrain (vertice.x, vertice.y, vertice.dir) trainsleft = trainsleft - 1 buyableTrains = buyableTrains - 1 numberOfTrains = numberOfTrains +1 end function ai.newTrain () end trains = {} function ai.newTrain (train) trains [train.ID] = {} end function ai.enoughMoney (money) buyableTrains = getMoney() / 25 if buyableTrains < 1 then return end local minDist = numberOfVertices local bestStartVertice local n = 0 local maxIterationTime = 0 local lastIteration = getNumberOfLines() for i, pas in pairs (passengers) do if pas.train == nil and pas.reservedForTrain == nil then local startVertice, endVertice, dist = nearestVertices (pas.x, pas.y, pas.destX, pas.destY) dist = getPassengerPriority (pas, dist) if dist < minDist then minDist = dist bestStartVertice = startVertice end end n = n+1 local numLine, totLine = getNumberOfLines() local diffLine = numLine - lastIteration if diffLine > maxIterationTime then maxIterationTime = diffLine end if numLine+2*maxIterationTime > totLine then break end end if bestStartVertice then buyNewTrain (bestStartVertice) end end function printMap() local map = theMap local str = {} for j = 1, map.height do str[j] = "map[" ..j.."]={" for i = 1, map.width do if i == 1 then str[j] = str[j] .. "\"" else str[j] = str[j] .. "\",\"" end if map[i][j] then str[j] = str[j] .. map[i][j] .. " " else str[j] = str[j] .. " " end end str[j] = str[j] .. "}" end for i = 1, #str do print(str[i]) end end