--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 sualk: -- Marvin2 -- Author: Klaus Flittner 1 and map[x][y-1] == 'C' then dirs.dirs['N'] = true dirs.count = dirs.count + 1 end if y < map.height and map[x][y+1] == 'C' then dirs.dirs['S'] = true dirs.count = dirs.count + 1 end if x < map.width and map[x+1][y] == 'C' then dirs.dirs['E'] = true dirs.count = dirs.count + 1 end if x > 1 and map[x-1][y] == 'C' then dirs.dirs['W'] = true dirs.count = dirs.count + 1 end return dirs end function pathToJunction(pos, dir) local path = {length = 0, map = {}, mIn = {}, mOut = {}} local startPos, startDir = pos, dir path.map[pos] = 0 path.mOut[pos] = dir repeat pos = pos + (directionDelta[dir].dy * mapWidth) + directionDelta[dir].dx path.length = path.length + 1 if not path.map[pos] then path.mIn[pos] = dir end if directionMap[pos].count == 1 then dir = reverseDir[dir] end if (directionMap[pos].count == 2) and (directionMap[pos].dirs[dir] == nil) then if directionMap[pos].dirs[orthoDir[dir][1]] then dir = orthoDir[dir][1] else dir = orthoDir[dir][2] end end if not path.map[pos] then path.map[pos] = path.length path.mOut[pos] = dir end until directionMap[pos].count > 2 or (pos == startPos and dir == startDir) path.mOut[pos] = nil return path, pos, dir end function move(pos, dir) if directionMap[pos].count == 1 then dir = reverseDir[dir] end if (directionMap[pos].count == 2) and (directionMap[pos].dirs[dir] == nil) then if directionMap[pos].dirs[orthoDir[dir][1]] then dir = orthoDir[dir][1] else dir = orthoDir[dir][2] end end return dir end function ai.init(map, money, trainLimit) mapWidth = map.width mapHeight = map.height maxTrains = trainLimit local distMap = {} for _, coord in ipairs(map.railList) do local pos = coord.y*mapWidth + coord.x directionMap[pos] = getDirs(map, coord.x, coord.y) if directionMap[pos].count > 2 then nodeMap[pos] = {x=coord.x, y=coord.y, connections={}} end end for pos, node in pairs(nodeMap) do for dir in pairs(directionMap[pos].dirs) do if not node.connections[dir] then local connection = {} local nextPos, nextDir connection.path, nextPos, nextDir = pathToJunction(pos, dir) connection.neighNode = nodeMap[nextPos] connection.neighDir = nextDir connection.node = node connection.dir = dir connection.reachable = {} connection.rIn = {} connection.rOut = {} connection.toUpdate = nil for pos, dist in pairs(connection.path.map) do connection.reachable[pos] = dist connection.rIn[pos] = connection.path.mIn[pos] connection.rOut[pos] = connection.path.mOut[pos] end connection.toUpdate = connection.reachable node.connections[dir] = connection distUpdate[connection] = true if connection.neighNode ~= node then local neighConnection = { path = connection.path, neighNode = node, neighDir = reverseDir[dir], node = connection.neighNode, dir = reverseDir[nextDir], reachable = {}, rIn = {}, rOut = {}, toUpdate = nil } for pos, dist in pairs(connection.path.map) do neighConnection.reachable[pos] = connection.path.length - dist neighConnection.rIn[pos] = reverseDir[connection.path.mOut[pos]] neighConnection.rOut[pos] = reverseDir[connection.path.mIn[pos]] end neighConnection.toUpdate = neighConnection.reachable connection.neighNode.connections[reverseDir[nextDir]] = neighConnection distUpdate[neighConnection] = true end end end end print("basic init finished.") propagateDistMap() print("init: "..getNumberOfLines()) end function propagateDistMap() local conn = next(distUpdate) if conn == nil then return end while conn do local node = conn.node for dir, altConn in pairs(node.connections) do if altConn ~= conn then local revConn = altConn.neighNode.connections[reverseDir[altConn.neighDir]] for pos, dist in pairs(conn.toUpdate) do if not revConn.reachable[pos] or revConn.reachable[pos] > dist + revConn.path.length then revConn.reachable[pos] = dist + revConn.path.length revConn.rIn[pos] = conn.rIn[pos] revConn.rOut[pos] = conn.rOut[pos] revConn.toUpdate[pos] = dist + revConn.path.length distUpdate[revConn] = true end end end end conn.toUpdate = {} distUpdate[conn] = nil conn = next(distUpdate) end print("finished propagating distances.") end function getNewTrain() local minDist, minDir = 10000, '' local dist, dir local minPass if curTrains == maxTrains or getMoney() < 25 then return end for name, pass in pairs(passengers) do dist, dir = updatePassDist(pass) if dist and dist < minDist then minDir = dir minDist = dist minPass = pass end end buyTrain(minPass.x, minPass.y, minDir) end function ai.enoughMoney(money) getNewTrain() propagateDistMap() end function ai.foundPassengers(gTrain, foundPassengers) local train = trains[gTrain.ID] local trainPos = gTrain.y * mapWidth + gTrain.x local minDist, minI = 10000, nil local dist, destDir if train.passenger then minDist = getDist(trainPos, gTrain.dir, train.passenger.dest) - 2 elseif train.nextPassenger then local name = train.nextPassenger.name for i, pass in pairs(foundPassengers) do if pass.name == name then minI = i minDist = getDist(trainPos, gTrain.dir, train.nextPassenger.dest) end end if not minI then minDist, destDir = getDist(trainPos, gTrain.dir, train.nextPassenger.pos) minDist = minDist + getPassDistIn(train.nextPassenger, destDir) end end for i, pass in pairs(foundPassengers) do dist = getPassDist(passengers[pass.name], gTrain.dir) if dist and dist < minDist then minDist = dist minI = i end end if minI then dropPassenger(gTrain) train.passenger = passengers[foundPassengers[minI].name] passengerRemove(train.passenger.name) train.nextPassenger = nil return foundPassengers[minI] end end function getMinDir(pos, curDir, dest) local minDir, minDist = '', 10000 local destDir = '' for dir, conn in pairs(nodeMap[pos].connections) do if dir ~= reverseDir[curDir] then if conn.reachable[dest] then if conn.reachable[dest] < minDist then minDir = dir minDist = conn.reachable[dest] destDir = conn.rIn[dest] end end end end return minDir, minDist, destDir end function ai.chooseDirection(gTrain, possibleDirections) local train = trains[gTrain.ID] local trainPos = gTrain.y * mapWidth + gTrain.x local nextPos = gTrain.nextY * mapWidth + gTrain.nextX local minDir, minDist = '', 10000 local minPass = nil local dir, destDir local dist if train.passenger and nextPos ~= train.passenger.dest then minDir, minDist = getMinDir(nextPos, gTrain.dir, train.passenger.dest) minDist = minDist - 2 elseif train.nextPassenger then if train.nextPassenger.pos == nextPos then minDir, minDist = getMinDir(nextPos, gTrain.dir, train.nextPassenger.dest) else minDir, minDist, destDir = getMinDir(nextPos, gTrain.dir, train.nextPassenger.pos) minDist = minDist + getPassDistIn(train.nextPassenger, destDir) end minPass = train.nextPassenger end for name, pass in pairs(passengers) do if passengersPos[nextPos] and passengersPos[nextPos][name] then dir, dist = getMinDir(nextPos, gTrain.dir, pass.dest) else dir, dist, destDir = getMinDir(nextPos, gTrain.dir, pass.pos) dist = dist + getPassDistIn(pass, destDir) end if dist < minDist then minDist, minDir = dist, dir minPass = pass end end if minPass and minPass ~= train.nextPassenger then train.nextPassenger = minPass end if minDir ~= '' then return minDir end propagateDistMap() end function ai.foundDestination(gTrain) dropPassenger(gTrain) trains[gTrain.ID].passenger = nil propagateDistMap() end function ai.newTrain(gTrain) trains[gTrain.ID] = {passenger = nil, nextPassenger = nil} curTrains = curTrains + 1 propagateDistMap() end function ai.blocked(train, possibleDirections, prevDirection) propagateDistMap() end function getDist(pos, dir, dest) local path, npos, ndir local node local dist, destDir path, npos, ndir = pathToJunction(pos, dir) if path.map[dest] then return path.map[dest], path.mIn[dest] end node = nodeMap[npos] for dir, conn in pairs(node.connections) do if dir ~= reverseDir[ndir] then if conn.reachable[dest] and (dist == nil or conn.reachable[dest] < dist) then dist = conn.reachable[dest] destDir = conn.rIn[dest] end end end if dist then dist = dist + path.length end return dist, destDir end function getPassDist(pass, dir) local dist if not pass then return nil end if pass.dist[dir] then return pass.dist[dir] else dist = getDist(pass.pos, dir, pass.dest) if not next(distUpdate) then pass.dist[dir] = dist end return dist end end function getPassDistIn(pass, dir) local minDist if nodeMap[pass.pos] then _, minDist, _ = getMinDir(pass.pos, dir, pass.dest) else minDist = getPassDist(pass, move(pass.pos, dir)) end return minDist end function updatePassDist(pass) local dist local minDir, minDist = '', 10000 for dir in pairs(directionMap[pass.pos].dirs) do if pass.dist[dir] == nil then dist = getDist(pass.pos, dir, pass.dest) if dist and not next(distUpdate) then pass.dist[dir] = dist end else dist = pass.dist[dir] end if dist and dist < minDist then minDist = dist minDir = dir end end return minDist, minDir end function ai.newPassenger(name, x, y, destX, destY, vipTime) local passenger = { name = name, x = x, y = y, pos = y * mapWidth + x, dest = destY * mapWidth + destX, vipTime = vipTime, dist = {} } passengerAdd(passenger) countPassengers = countPassengers + 1 if countPassengers > 3 then getNewTrain() end propagateDistMap() end function ai.passengerBoarded(gTrain, passengerName) passengerRemove(passengerName) propagateDistMap() end function passengerAdd(pass) if not pass then return end passengers[pass.name] = pass if not passengersPos[pass.pos] then passengersPos[pass.pos] = {} end passengersPos[pass.pos][pass.name] = pass end function passengerRemove(passName) local pass = passengers[passName] if pass then passengers[pass.name] = nil passengersPos[pass.pos][pass.name] = nil end for id, train in pairs(trains) do if train.nextPassenger and train.nextPassenger.name == passName then train.nextPassenger = nil end end end