1
0
mirror of https://github.com/veden/Rampant.git synced 2024-12-26 20:54:12 +02:00

trying to better represent map features

This commit is contained in:
Aaron Veden 2017-12-28 21:38:10 -08:00
parent d0ed774587
commit 319fb66845
16 changed files with 569 additions and 431 deletions

View File

@ -88,8 +88,7 @@ local function onIonCannonFired(event)
if (natives.points > AI_MAX_OVERFLOW_POINTS) then
natives.points = AI_MAX_OVERFLOW_POINTS
end
local chunkX, chunkY = positionToChunkXY(event.position)
local chunk = getChunkByPosition(regionMap, chunkX, chunkY)
local chunk = getChunkByPosition(regionMap, event.position)
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
rallyUnits(chunk, regionMap, surface, natives, event.tick)
end
@ -158,10 +157,13 @@ local function rebuildRegionMap()
y=0}
--this is shared between two different queries
local sharedFilterArea = {{0, 0}, {0, 0}}
regionMap.filteredEntitiesQuery = { area=sharedFilterArea, force=false }
regionMap.cliffQuery = { type="cliff", area={{0, 0}, {0, 0}} }
regionMap.filteredTilesQuery = { name="", area=sharedFilterArea }
regionMap.area = {{0, 0}, {0, 0}}
regionMap.countResourcesQuery = { area=regionMap.area, type="resource" }
regionMap.filteredEntitiesEnemyQuery = { area=regionMap.area, force="enemy" }
regionMap.filteredEntitiesEnemyTypeQuery = { area=regionMap.area, force="enemy", type="unit-spawner" }
regionMap.filteredEntitiesPlayerQuery = { area=regionMap.area, force="player" }
regionMap.canPlaceQuery = { name="", position={0,0} }
regionMap.filteredTilesQuery = { name="", area=regionMap.area }
-- switched over to tick event
regionMap.logicTick = roundToNearest(game.tick + INTERVAL_LOGIC, INTERVAL_LOGIC)
@ -253,28 +255,28 @@ local function onTick(event)
processPendingChunks(natives, regionMap, surface, pendingChunks, tick)
scanMap(regionMap, surface, natives)
if (tick == regionMap.logicTick) then
regionMap.logicTick = regionMap.logicTick + INTERVAL_LOGIC
-- if (tick == regionMap.logicTick) then
-- regionMap.logicTick = regionMap.logicTick + INTERVAL_LOGIC
local players = gameRef.players
-- local players = gameRef.players
planning(natives,
gameRef.forces.enemy.evolution_factor,
tick,
surface)
-- planning(natives,
-- gameRef.forces.enemy.evolution_factor,
-- tick,
-- surface)
cleanSquads(natives)
regroupSquads(natives)
-- cleanSquads(natives)
-- regroupSquads(natives)
processPlayers(players, regionMap, surface, natives, tick)
-- processPlayers(players, regionMap, surface, natives, tick)
if natives.useCustomAI then
processBases(regionMap, surface, natives, tick)
end
-- if natives.useCustomAI then
-- processBases(regionMap, surface, natives, tick)
-- end
squadsBeginAttack(natives, players)
squadsAttack(regionMap, surface, natives)
end
-- squadsBeginAttack(natives, players)
-- squadsAttack(regionMap, surface, natives)
-- end
processMap(regionMap, surface, natives, tick)
end
@ -299,19 +301,18 @@ local function onDeath(event)
local surface = entity.surface
if (surface.index == 1) then
local entityPosition = entity.position
local chunkX, chunkY = positionToChunkXY(entityPosition)
local chunk = getChunkByPosition(regionMap, entityPosition)
if (entity.force.name == "enemy") then
if (entity.type == "unit") then
local deathChunk = getChunkByPosition(regionMap, chunkX, chunkY)
if (deathChunk ~= SENTINEL_IMPASSABLE_CHUNK) then
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
-- drop death pheromone where unit died
deathScent(deathChunk)
deathScent(chunk)
if event.force and (event.force.name == "player") and (deathChunk[MOVEMENT_PHEROMONE] < natives.retreatThreshold) then
if event.force and (event.force.name == "player") and (chunk[MOVEMENT_PHEROMONE] < natives.retreatThreshold) then
local tick = event.tick
retreatUnits(deathChunk,
retreatUnits(chunk,
entityPosition,
convertUnitGroupToSquad(natives, entity.unit_group),
regionMap,
@ -320,7 +321,7 @@ local function onDeath(event)
tick)
if (mRandom() < natives.rallyThreshold) and not surface.peaceful_mode then
rallyUnits(deathChunk, regionMap, surface, natives, tick)
rallyUnits(chunk, regionMap, surface, natives, tick)
end
end
end
@ -332,9 +333,8 @@ local function onDeath(event)
local creditNatives = false
if (event.force ~= nil) and (event.force.name == "enemy") then
creditNatives = true
local victoryChunk = getChunkByPosition(regionMap, chunkX, chunkY)
if (victoryChunk ~= SENTINEL_IMPASSABLE_CHUNK) then
victoryScent(victoryChunk, entity.type)
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
victoryScent(chunk, entity.type)
end
end
if creditNatives and natives.safeBuildings and (natives.safeEntities[entity.type] or natives.safeEntityName[entity.name]) then
@ -403,15 +403,13 @@ remote.add_interface("rampantTests",
cheatMode = tests.cheatMode,
gaussianRandomTest = tests.gaussianRandomTest,
reveal = tests.reveal,
showMovementGrid = tests.showMovementGrid,
baseStats = tests.baseStats,
baseTiles = tests.baseTiles,
mergeBases = tests.mergeBases,
clearBases = tests.clearBases,
getOffsetChunk = tests.getOffsetChunk,
registeredNest = tests.registeredNest,
colorResourcePoints = tests.colorResourcePoints,
stepAdvanceTendrils = tests.stepAdvanceTendrils
stepAdvanceTendrils = tests.stepAdvanceTendrils,
exportAiState = tests.exportAiState
}
)

View File

@ -46,7 +46,7 @@ local getRallyTick = chunkUtils.getRallyTick
local setRallyTick = chunkUtils.setRallyTick
local getNeighborChunks = mapUtils.getNeighborChunks
local getChunkByPosition = mapUtils.getChunkByPosition
local getChunkByXY = mapUtils.getChunkByXY
local scoreNeighborsForFormation = movementUtils.scoreNeighborsForFormation
local createSquad = unitGroupUtils.createSquad
local attackWaveScaling = config.attackWaveScaling
@ -94,7 +94,7 @@ function aiAttackWave.rallyUnits(chunk, regionMap, surface, natives, tick)
for x=cX - RALLY_CRY_DISTANCE, cX + RALLY_CRY_DISTANCE, 32 do
for y=cY - RALLY_CRY_DISTANCE, cY + RALLY_CRY_DISTANCE, 32 do
if (x ~= cX) and (y ~= cY) then
local rallyChunk = getChunkByPosition(regionMap, x, y)
local rallyChunk = getChunkByXY(regionMap, x, y)
if (rallyChunk ~= SENTINEL_IMPASSABLE_CHUNK) and (getNestCount(regionMap, rallyChunk) > 0) then
aiAttackWave.formSquads(regionMap, surface, natives, rallyChunk, AI_VENGENCE_SQUAD_COST)
if (natives.points < AI_VENGENCE_SQUAD_COST) and (#natives.squads < natives.maxSquads) then

View File

@ -14,22 +14,17 @@ local SENTINEL_IMPASSABLE_CHUNK = constants.SENTINEL_IMPASSABLE_CHUNK
-- imported functions
local createChunk = chunkUtils.createChunk
local checkChunkPassability = chunkUtils.checkChunkPassability
local scoreChunk = chunkUtils.scoreChunk
local registerChunkEnemies = chunkUtils.registerChunkEnemies
local analyzeChunk = chunkUtils.analyzeChunk
-- module code
function chunkProcessor.processPendingChunks(natives, regionMap, surface, pendingStack)
local processQueue = regionMap.processQueue
local filteredEntitiesQuery = regionMap.filteredEntitiesQuery
local area = regionMap.area
local topOffset = filteredEntitiesQuery.area[1]
local bottomOffset = filteredEntitiesQuery.area[2]
local filteredTilesQuery = regionMap.filteredTilesQuery
local cliffQuery = regionMap.cliffQuery
local topOffset = area[1]
local bottomOffset = area[2]
for i=#pendingStack, 1, -1 do
local event = pendingStack[i]
@ -45,12 +40,9 @@ function chunkProcessor.processPendingChunks(natives, regionMap, surface, pendin
bottomOffset[1] = x + CHUNK_SIZE
bottomOffset[2] = y + CHUNK_SIZE
chunk = checkChunkPassability(chunk, surface, filteredTilesQuery, cliffQuery)
chunk = analyzeChunk(chunk, natives, surface, regionMap)
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
registerChunkEnemies(regionMap, chunk, surface, filteredEntitiesQuery)
scoreChunk(regionMap, chunk, surface, natives, filteredEntitiesQuery)
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
local chunkX = chunk.x
if regionMap[chunkX] == nil then

View File

@ -28,7 +28,6 @@ local CHUNK_ALL_DIRECTIONS = constants.CHUNK_ALL_DIRECTIONS
local CHUNK_IMPASSABLE = constants.CHUNK_IMPASSABLE
local CHUNK_TICK = constants.CHUNK_TICK
local CHUNK_SIZE = constants.CHUNK_SIZE
local PATH_RATING = constants.PATH_RATING
@ -36,42 +35,42 @@ local PASSABLE = constants.PASSABLE
-- imported functions
local getChunkByPosition = mapUtils.getChunkByPosition
local getChunkByXY = mapUtils.getChunkByXY
local mFloor = math.floor
-- module code
local function fullScan(x, y, count_entities_filtered, cliffQuery)
local function fullScan(chunk, can_place_entity, canPlaceQuery)
local x = chunk.x
local y = chunk.y
local passableNorthSouth = false
local passableEastWest = false
local area = cliffQuery.area
local top = area[1]
local bottom = area[2]
top[2] = y
bottom[2] = y + CHUNK_SIZE
canPlaceQuery.name = "chunk-scanner-ns-rampant"
local position = canPlaceQuery.position
position[2] = y
for xi=x, x + 31 do
top[1] = xi
bottom[1] = xi + 1
if (count_entities_filtered(cliffQuery) == 0) then
for xi=x, x + 32 do
position[1] = xi
if can_place_entity(canPlaceQuery) then
passableNorthSouth = true
break
end
end
top[1] = x
bottom[1] = x + CHUNK_SIZE
for yi=y, y + 31 do
top[2] = yi
bottom[2] = yi + 1
if (count_entities_filtered(cliffQuery) == 0) then
position[1] = x
canPlaceQuery.name = "chunk-scanner-ew-rampant"
for yi=y, y + 32 do
position[2] = yi
if can_place_entity(canPlaceQuery) then
passableEastWest = true
break
end
end
return passableNorthSouth, passableEastWest
end
@ -140,7 +139,7 @@ local function getEntityOverlapChunks(regionMap, entity)
local leftBottomChunk = SENTINEL_IMPASSABLE_CHUNK
local rightBottomChunk = SENTINEL_IMPASSABLE_CHUNK
if (boundingBox ~= nil) then
if boundingBox then
local center = entity.position
local topXOffset
local topYOffset
@ -174,15 +173,15 @@ local function getEntityOverlapChunks(regionMap, entity)
local rightBottomChunkX = rightTopChunkX
local rightBottomChunkY = leftBottomChunkY
leftTopChunk = getChunkByPosition(regionMap, leftTopChunkX, leftTopChunkY)
leftTopChunk = getChunkByXY(regionMap, leftTopChunkX, leftTopChunkY)
if (leftTopChunkX ~= rightTopChunkX) then
rightTopChunk = getChunkByPosition(regionMap, rightTopChunkX, rightTopChunkY)
rightTopChunk = getChunkByXY(regionMap, rightTopChunkX, rightTopChunkY)
end
if (leftTopChunkY ~= leftBottomChunkY) then
leftBottomChunk = getChunkByPosition(regionMap, leftBottomChunkX, leftBottomChunkY)
leftBottomChunk = getChunkByXY(regionMap, leftBottomChunkX, leftBottomChunkY)
end
if (leftTopChunkX ~= rightBottomChunkX) and (leftTopChunkY ~= rightBottomChunkY) then
rightBottomChunk = getChunkByPosition(regionMap, rightBottomChunkX, rightBottomChunkY)
rightBottomChunk = getChunkByXY(regionMap, rightBottomChunkX, rightBottomChunkY)
end
end
return leftTopChunk, rightTopChunk, leftBottomChunk, rightBottomChunk
@ -190,9 +189,9 @@ end
-- external functions
function chunkUtils.checkChunkPassability(chunk, surface, filteredTilesQuery, cliffQuery)
function chunkUtils.analyzeChunk(chunk, natives, surface, regionMap)
local count_tiles_filtered = surface.count_tiles_filtered
local count_entities_filtered = surface.count_entities_filtered
local filteredTilesQuery = regionMap.filteredTilesQuery
local passScore = 0
for i=1,#WATER_TILE_NAMES do
@ -204,7 +203,10 @@ function chunkUtils.checkChunkPassability(chunk, surface, filteredTilesQuery, cl
local pass = CHUNK_IMPASSABLE
if (passScore >= 0.60) then
local passableNorthSouth, passableEastWest = fullScan(chunk.x, chunk.y, count_entities_filtered, cliffQuery)
local passableNorthSouth, passableEastWest = fullScan(chunk,
surface.can_place_entity,
regionMap.canPlaceQuery)
if passableEastWest and passableNorthSouth then
pass = CHUNK_ALL_DIRECTIONS
elseif passableEastWest then
@ -212,7 +214,54 @@ function chunkUtils.checkChunkPassability(chunk, surface, filteredTilesQuery, cl
elseif passableNorthSouth then
pass = CHUNK_NORTH_SOUTH
end
local entities = surface.find_entities_filtered(regionMap.filteredEntitiesPlayerQuery)
local playerObjects = 0
local safeBuildings = natives.safeBuildings
local safeEntities = natives.safeEntities
local safeEntityName = natives.safeEntityName
if safeBuildings then
for i=1, #entities do
local entity = entities[i]
local entityType = entity.type
if safeEntities[entityType] or safeEntityName[entity.name] then
entity.destructible = false
end
local entityScore = BUILDING_PHEROMONES[entityType] or 0
playerObjects = playerObjects + entityScore
end
else
for i=1, #entities do
local entityScore = BUILDING_PHEROMONES[entities[i].type] or 0
playerObjects = playerObjects + entityScore
end
end
local query = regionMap.filteredEntitiesEnemyTypeQuery
query.type = "unit-spawner"
local nests = surface.count_entities_filtered(regionMap.filteredEntitiesEnemyTypeQuery)
chunkUtils.setNestCount(regionMap, chunk, nests)
query.type = "turret"
local worms = surface.count_entities_filtered(regionMap.filteredEntitiesEnemyTypeQuery)
chunkUtils.setWormCount(regionMap, chunk, worms)
local resources = surface.count_entities_filtered(regionMap.countResourcesQuery) * 0.001
local total = nests + worms + resources
if (playerObjects > 0) then
pass = CHUNK_PLAYER_BORDER
end
chunkUtils.setPlayerBaseGenerator(regionMap, chunk, playerObjects)
chunkUtils.setResourceGenerator(regionMap, chunk, resources)
end
if (pass == CHUNK_IMPASSABLE) then
return SENTINEL_IMPASSABLE_CHUNK
else
@ -247,16 +296,27 @@ end
-- -- end
-- end
function chunkUtils.registerChunkEnemies(regionMap, chunk, surface, filteredEntitiesQuery)
filteredEntitiesQuery.force = "enemy"
local enemies = surface.find_entities_filtered(filteredEntitiesQuery)
function chunkUtils.getNestCount(regionMap, chunk)
return regionMap.chunkToNests[chunk] or 0
end
for i=1, #enemies do
local enemy = enemies[i]
local enemyType = enemy.type
if (enemyType == "unit-spawner") or (enemyType == "turret") then
addEnemyStructureToChunk(regionMap, chunk, enemy, nil)
end
function chunkUtils.getWormCount(regionMap, chunk)
return regionMap.chunkToWorms[chunk] or 0
end
function chunkUtils.setWormCount(regionMap, chunk, count)
if (count == 0) then
regionMap.chunkToWorms[chunk] = nil
else
regionMap.chunkToWorms[chunk] = count
end
end
function chunkUtils.setNestCount(regionMap, chunk, count)
if (count == 0) then
regionMap.chunkToNests[chunk] = nil
else
regionMap.chunkToNests[chunk] = count
end
end
@ -288,8 +348,12 @@ function chunkUtils.setRetreatTick(regionMap, chunk, tick)
regionMap.chunkToRetreats[chunk] = tick
end
function chunkUtils.setResourceGenerator(regionMap, chunk, playerGenerator)
regionMap.chunkToResource[chunk] = playerGenerator
function chunkUtils.setResourceGenerator(regionMap, chunk, resourceGenerator)
if (resourceGenerator == 0) then
regionMap.chunkToResource[chunk] = nil
else
regionMap.chunkToResource[chunk] = resourceGenerator
end
end
function chunkUtils.getResourceGenerator(regionMap, chunk)
@ -301,43 +365,17 @@ function chunkUtils.getPlayerBaseGenerator(regionMap, chunk)
end
function chunkUtils.setPlayerBaseGenerator(regionMap, chunk, playerGenerator)
regionMap.chunkToPlayerBase[chunk] = playerGenerator
if (playerGenerator == 0) then
regionMap.chunkToPlayerBase[chunk] = nil
else
regionMap.chunkToPlayerBase[chunk] = playerGenerator
end
end
function chunkUtils.addPlayerBaseGenerator(regionMap, chunk, playerGenerator)
regionMap.chunkToPlayerBase[chunk] = regionMap.chunkToPlayerBase[chunk] + playerGenerator
end
function chunkUtils.scoreChunk(regionMap, chunk, surface, natives, filteredEntitiesQuery)
filteredEntitiesQuery.force = nil
filteredEntitiesQuery.type = "resource"
chunkUtils.setResourceGenerator(regionMap, chunk, surface.count_entities_filtered(filteredEntitiesQuery) * 0.001)
filteredEntitiesQuery.type = nil
filteredEntitiesQuery.force = "player"
local entities = surface.find_entities_filtered(filteredEntitiesQuery)
local playerObjects = 0
local safeBuildings = natives.safeBuildings
for i=1, #entities do
local entity = entities[i]
local entityType = entity.type
if safeBuildings then
if natives.safeEntities[entityType] or natives.safeEntityName[entity.name] then
entity.destructible = false
end
end
local entityScore = BUILDING_PHEROMONES[entityType]
if entityScore then
playerObjects = playerObjects + entityScore
end
end
chunkUtils.setPlayerBaseGenerator(regionMap, chunk, playerObjects)
end
function chunkUtils.createChunk(topX, topY)
local chunk = {
x = topX,
@ -354,16 +392,6 @@ function chunkUtils.createChunk(topX, topY)
return chunk
end
function chunkUtils.colorChunk(x, y, tileType, surface)
local tiles = {}
for xi=x+5, x + 27 do
for yi=y+5, y + 27 do
tiles[#tiles+1] = {name=tileType, position={xi, yi}}
end
end
surface.set_tiles(tiles, false)
end
function chunkUtils.registerEnemyBaseStructure(regionMap, entity, base)
local entityType = entity.type
if ((entityType == "unit-spawner") or (entityType == "turret")) and (entity.force.name == "enemy") then

View File

@ -18,7 +18,6 @@ constants.VERSION_28 = 28
constants.VERSION_33 = 33
constants.VERSION_36 = 36
-- misc
constants.WATER_TILE_NAMES = { "water", "deepwater", "water-green", "deepwater-green" }
@ -30,13 +29,10 @@ constants.RETREAT_MOVEMENT_PHEROMONE_LEVEL = 10000
constants.PROCESS_QUEUE_SIZE = 400
constants.SCAN_QUEUE_SIZE = 5
constants.ITEM_COLLECTOR_QUEUE_SIZE = 6
constants.BASE_QUEUE_SIZE = 1
constants.SQUAD_QUEUE_SIZE = 2
constants.PROCESS_PLAYER_BOUND = 128
constants.ITEM_COLLECTOR_MAX_QUEUE_SIZE = 20
constants.TICKS_A_SECOND = 60
constants.TICKS_A_MINUTE = constants.TICKS_A_SECOND * 60
@ -47,22 +43,6 @@ constants.PLAYER_PHEROMONE_MULTIPLER = 100
constants.DEV_CUSTOM_AI = false
-- mask
constants.MASK_PASSABLE = 3
constants.MASK_PASSABLE_SIZE = 2
constants.MASK_NEST_COUNT = 511
constants.MASK_NEST_COUNT_SIZE = 9
constants.MASK_WORM_COUNT = 511
constants.MASK_WORM_COUNT_SIZE = 9
constants.MASK_PASSABLE_AND_NEST_COUNT = 2047
constants.MASK_PASSABLE_AND_NEST_COUNT_SIZE = 11
-- item collector
constants.ITEM_COLLECTOR_DISTANCE = 50
-- chunk properties
constants.CHUNK_SIZE = 32
@ -75,6 +55,8 @@ constants.CHUNK_IMPASSABLE = 0
constants.CHUNK_NORTH_SOUTH = 1
constants.CHUNK_EAST_WEST = 2
constants.CHUNK_ALL_DIRECTIONS = 3
constants.CHUNK_PLAYER_BORDER = 4
constants.CHUNK_PLAYER_INTERIOR = 5
-- ai

View File

@ -42,16 +42,16 @@ local playerScent = pheromoneUtils.playerScent
local formSquads = aiAttackWave.formSquads
local getChunkByPosition = mapUtils.getChunkByPosition
local positionToChunkXY = mapUtils.positionToChunkXY
local getChunkByXY = mapUtils.getChunkByXY
local recycleBiters = unitGroupUtils.recycleBiters
local validPlayer = playerUtils.validPlayer
local setResourceGenerator = chunkUtils.setResourceGenerator
local setPlayerBaseGenerator = chunkUtils.setPlayerBaseGenerator
local analyzeChunk = chunkUtils.analyzeChunk
local getNestCount = chunkUtils.getNestCount
local getWormCount = chunkUtils.getWormCount
local getEnemyStructureCount = chunkUtils.getEnemyStructureCount
local canAttack = aiPredicates.canAttack
@ -141,8 +141,7 @@ function mapProcessor.processPlayers(players, regionMap, surface, natives, tick)
for i=1,#playerOrdering do
local player = players[playerOrdering[i]]
if validPlayer(player) then
local chunkX, chunkY = positionToChunkXY(player.character.position)
local playerChunk = getChunkByPosition(regionMap, chunkX, chunkY)
local playerChunk = getChunkByPosition(regionMap, player.character.position)
if (playerChunk ~= SENTINEL_IMPASSABLE_CHUNK) then
playerScent(playerChunk)
@ -152,17 +151,16 @@ function mapProcessor.processPlayers(players, regionMap, surface, natives, tick)
for i=1,#playerOrdering do
local player = players[playerOrdering[i]]
if validPlayer(player) then
local chunkX, chunkY = positionToChunkXY(player.character.position)
local playerChunk = getChunkByPosition(regionMap, chunkX, chunkY)
local playerChunk = getChunkByPosition(regionMap, player.character.position)
if (playerChunk ~= SENTINEL_IMPASSABLE_CHUNK) then
local vengence = (allowingAttacks and
(natives.points >= AI_VENGENCE_SQUAD_COST) and
((getWormCount(regionMap, playerChunk) > 0) or (getNestCount(regionMap, playerChunk) > 0) or (playerChunk[MOVEMENT_PHEROMONE] < natives.retreatThreshold)))
((getEnemyStructureCount(regionMap, playerChunk) > 0) or (playerChunk[MOVEMENT_PHEROMONE] < natives.retreatThreshold)))
for x=playerChunk.x - PROCESS_PLAYER_BOUND, playerChunk.x + PROCESS_PLAYER_BOUND, 32 do
for y=playerChunk.y - PROCESS_PLAYER_BOUND, playerChunk.y + PROCESS_PLAYER_BOUND, 32 do
local chunk = getChunkByPosition(regionMap, x, y)
local chunk = getChunkByXY(regionMap, x, y)
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) and (chunk[CHUNK_TICK] ~= tick) then
chunk[CHUNK_TICK] = tick
@ -197,10 +195,6 @@ function mapProcessor.scanMap(regionMap, surface, natives)
local offset = {0, 0}
local chunkBox = {false, offset}
local playerQuery = {area = chunkBox,
force = "player"}
local resourceQuery = {area = chunkBox,
type = "resource"}
local unitCountQuery = { area = chunkBox,
type = "unit",
force = "enemy",
@ -235,25 +229,7 @@ function mapProcessor.scanMap(regionMap, surface, natives)
end
end
setResourceGenerator(regionMap, chunk, surface.count_entities_filtered(resourceQuery) * 0.001)
local entities = surface.find_entities_filtered(playerQuery)
local playerBaseGenerator = 0
local safeBuildings = natives.safeBuildings
for i=1,#entities do
local entity = entities[i]
local value = BUILDING_PHEROMONES[entity.type]
if safeBuildings then
if natives.safeEntities[entity.type] or natives.safeEntityName[entity.name] then
entity.destructible = false
end
end
if value then
playerBaseGenerator = playerBaseGenerator + value
end
end
setPlayerBaseGenerator(regionMap, chunk, playerBaseGenerator)
analyzeChunk(chunk, natives, surface, regionMap)
end
if (endIndex == #processQueue) then

View File

@ -25,7 +25,7 @@ local mFloor = math.floor
-- module code
function mapUtils.getChunkByPosition(regionMap, x, y)
function mapUtils.getChunkByXY(regionMap, x, y)
local chunkX = regionMap[x]
if chunkX then
return chunkX[y] or SENTINEL_IMPASSABLE_CHUNK
@ -33,6 +33,15 @@ function mapUtils.getChunkByPosition(regionMap, x, y)
return SENTINEL_IMPASSABLE_CHUNK
end
function mapUtils.getChunkByPosition(regionMap, position)
local chunkX = regionMap[mFloor(position.x * CHUNK_SIZE_DIVIDER) * CHUNK_SIZE]
if chunkX then
local chunkY = mFloor(position.y * CHUNK_SIZE_DIVIDER) * CHUNK_SIZE
return chunkX[chunkY] or SENTINEL_IMPASSABLE_CHUNK
end
return SENTINEL_IMPASSABLE_CHUNK
end
function mapUtils.positionToChunkXY(position)
local chunkX = mFloor(position.x * CHUNK_SIZE_DIVIDER) * CHUNK_SIZE
local chunkY = mFloor(position.y * CHUNK_SIZE_DIVIDER) * CHUNK_SIZE
@ -126,33 +135,32 @@ function mapUtils.getCardinalChunks(regionMap, x, y)
return neighbors
end
function mapUtils.positionFromDirectionAndChunk(direction, startPosition, position, scaling)
function mapUtils.positionFromDirectionAndChunk(direction, startPosition, endPosition, scaling)
if (direction == 1) then
position.x = startPosition.x - CHUNK_SIZE * scaling
position.y = startPosition.y - CHUNK_SIZE * scaling
endPosition.x = startPosition.x - CHUNK_SIZE * scaling
endPosition.y = startPosition.y - CHUNK_SIZE * scaling
elseif (direction == 2) then
position.x = startPosition.x
position.y = startPosition.y - CHUNK_SIZE * scaling
endPosition.x = startPosition.x
endPosition.y = startPosition.y - CHUNK_SIZE * scaling
elseif (direction == 3) then
position.x = startPosition.x + CHUNK_SIZE * scaling
position.y = startPosition.y - CHUNK_SIZE * scaling
endPosition.x = startPosition.x + CHUNK_SIZE * scaling
endPosition.y = startPosition.y - CHUNK_SIZE * scaling
elseif (direction == 4) then
position.x = startPosition.x - CHUNK_SIZE * scaling
position.y = startPosition.y
endPosition.x = startPosition.x - CHUNK_SIZE * scaling
endPosition.y = startPosition.y
elseif (direction == 5) then
position.x = startPosition.x + CHUNK_SIZE * scaling
position.y = startPosition.y
endPosition.x = startPosition.x + CHUNK_SIZE * scaling
endPosition.y = startPosition.y
elseif (direction == 6) then
position.x = startPosition.x - CHUNK_SIZE * scaling
position.y = startPosition.y + CHUNK_SIZE * scaling
endPosition.x = startPosition.x - CHUNK_SIZE * scaling
endPosition.y = startPosition.y + CHUNK_SIZE * scaling
elseif (direction == 7) then
position.x = startPosition.x
position.y = startPosition.y + CHUNK_SIZE * scaling
endPosition.x = startPosition.x
endPosition.y = startPosition.y + CHUNK_SIZE * scaling
elseif (direction == 8) then
position.x = startPosition.x + CHUNK_SIZE * scaling
position.y = startPosition.y + CHUNK_SIZE * scaling
endPosition.x = startPosition.x + CHUNK_SIZE * scaling
endPosition.y = startPosition.y + CHUNK_SIZE * scaling
end
return position
end
return mapUtils

View File

@ -33,7 +33,7 @@ local getChunkByPosition = mapUtils.getChunkByPosition
function nestUtils.buildNest(regionMap, base, surface, targetPosition, name)
local position = surface.find_non_colliding_position(name, targetPosition, DOUBLE_CHUNK_SIZE, 2)
local chunk = getChunkByPosition(regionMap, position.x, position.y)
local chunk = getChunkByPosition(regionMap, position)
local nest = nil
if position and (chunk ~= SENTINEL_IMPASSABLE_CHUNK) and (chunk[NEST_COUNT] < 3) then
local biterSpawner = {name=name, position=position}

View File

@ -30,8 +30,7 @@ local PATH_RATING = constants.PATH_RATING
local getCardinalChunks = mapUtils.getCardinalChunks
local mMax = math.max
local getNestCount = chunkUtils.getNestCount
local getWormCount = chunkUtils.getWormCount
local getEnemyStructureCount = chunkUtils.getEnemyStructureCount
local getPlayerBaseGenerator = chunkUtils.getPlayerBaseGenerator
local getResourceGenerator = chunkUtils.getResourceGenerator
@ -40,7 +39,7 @@ local getResourceGenerator = chunkUtils.getResourceGenerator
function pheromoneUtils.scents(regionMap, chunk)
chunk[BASE_PHEROMONE] = chunk[BASE_PHEROMONE] + getPlayerBaseGenerator(regionMap, chunk)
local resourceGenerator = getResourceGenerator(regionMap, chunk)
if (resourceGenerator > 0) and (getNestCount(regionMap, chunk) == 0) and (getWormCount(regionMap, chunk) == 0) then
if (resourceGenerator > 0) and (getEnemyStructureCount(regionMap, chunk) == 0) then
chunk[RESOURCE_PHEROMONE] = chunk[RESOURCE_PHEROMONE] + mMax(resourceGenerator * 100, 90)
end
end

View File

@ -83,11 +83,10 @@ function squadAttack.squadsAttack(regionMap, surface, natives)
local groupState = group.state
if (groupState == DEFINES_GROUP_FINISHED) or (groupState == DEFINES_GROUP_GATHERING) or ((groupState == DEFINES_GROUP_MOVING) and (squad.cycles == 0)) then
local groupPosition = group.position
local chunkX, chunkY = positionToChunkXY(groupPosition)
local chunk = getChunkByPosition(regionMap, chunkX, chunkY)
local chunk = getChunkByPosition(regionMap, groupPosition)
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
local attackChunk, attackDirection = scoreNeighborsForAttack(chunk,
getNeighborChunks(regionMap, chunkX, chunkY),
getNeighborChunks(regionMap, chunk.x, chunk.y),
scoreAttackLocation,
squad)
addMovementPenalty(natives, squad, chunk)
@ -136,9 +135,9 @@ function squadAttack.squadsBeginAttack(natives, players)
local squad = squads[i]
local group = squad.group
if (squad.status == SQUAD_GUARDING) and group.valid then
local groupPosition = group.position
local kamikazeThreshold = calculateKamikazeThreshold(squad, natives)
local groupPosition = group.position
if playersWithinProximityToPosition(players, groupPosition, 100) then
squad.frenzy = true
squad.frenzyPosition.x = groupPosition.x

View File

@ -67,7 +67,7 @@ local function buildTendrilPath(regionMap, tendril, surface, base, tick, natives
return
end
local tendrilPosition = tendrilUnit.position
local chunk = getChunkByPosition(regionMap, tendrilPosition.x, tendrilPosition.y)
local chunk = getChunkByPosition(regionMap, tendrilPosition)
if (chunk ~= SENTINEL_IMPASSABLE_CHUNK) then
local tendrilPath,tendrilDirection = scoreNeighborsForResource(chunk,
getNeighborChunks(regionMap, chunk.x, chunk.y),

View File

@ -81,7 +81,5 @@
(define (run)
(copyFiles modFolder)
;;(copyFiles zipModFolder)
;;(makeZip modFolder)
)
)
(system*/exit-code "/data/games/factorio/bin/x64/factorio")))

99
parseState.rkt Executable file
View File

@ -0,0 +1,99 @@
(module AiState racket
(provide (all-defined-out))
(struct AiState (chunks
chunksLookup
minMaxes)
#:transparent)
(struct MinMax (min max)
#:transparent)
(struct ChunkRange (x
y
movement
base
player
resource
passable
tick
rating)
#:transparent)
(struct Chunk (x
y
movement
base
player
resource
passable
tick
rating)
#:transparent)
(require threading)
(define (getFile filePath)
(call-with-input-file filePath
(lambda (port)
(port->string port))))
(define (stringToChunk str)
(match-let (((list movement base player resource passable tick rating x y) (string-split (substring str
12
(- (string-length str)
14))
",")))
(apply Chunk
(map (lambda (x)
(string->number (second (string-split x
"="))))
(list x y movement base player resource passable tick rating)))))
(define (chunk->string chunk)
(string-append "x: " (~v (Chunk-x chunk)) "\n"
"y: " (~v (Chunk-y chunk)) "\n"
"movement: " (~v (Chunk-movement chunk)) "\n"
"base: " (~v (Chunk-base chunk)) "\n"
"player: " (~v (Chunk-player chunk)) "\n"
"resource: " (~v (Chunk-resource chunk)) "\n"
"passable: " (~v (Chunk-passable chunk)) "\n"
"tick: " (~v (Chunk-tick chunk)) "\n"
"rating: " (~v (Chunk-rating chunk)) "\n"))
(define (findChunkPropertiesMinMax chunks)
(let ((xs (map Chunk-x chunks))
(ys (map Chunk-y chunks))
(movements (map Chunk-movement chunks))
(bases (map Chunk-base chunks))
(players (map Chunk-player chunks))
(resources (map Chunk-resource chunks))
(passables (map Chunk-passable chunks))
(ticks (map Chunk-tick chunks))
(ratings (map Chunk-rating chunks)))
(ChunkRange (MinMax (apply min xs) (apply max xs))
(MinMax (apply min ys) (apply max ys))
(MinMax (apply min movements) (apply max movements))
(MinMax (apply min bases) (apply max bases))
(MinMax (apply min players) (apply max players))
(MinMax (apply min resources) (apply max resources))
(MinMax (apply min passables) (apply max passables))
(MinMax (apply min ticks) (apply max ticks))
(MinMax (apply min ratings) (apply max ratings)))))
(define (readState filePath)
(let* ((replayChunks (getFile filePath))
(chunks (map stringToChunk (string-split replayChunks "\n")))
(minMaxes (findChunkPropertiesMinMax chunks)))
(AiState chunks
(apply hash
(apply append
(map (lambda (chunk)
(list (list (Chunk-x chunk)
(Chunk-y chunk))
chunk))
chunks)))
minMaxes)))
(define (test)
(readState "/data/games/factorio/script-output/rampantState.txt")))

View File

@ -189,187 +189,63 @@ data:extend({
},
{
type = "unit-spawner",
name = "chunk-scanner-ew-rampant",
icon = "__base__/graphics/icons/biter-spawner.png",
type = "container",
name = "chunk-scanner-ns-rampant",
icon = "__base__/graphics/icons/wooden-chest.png",
icon_size = 32,
flags = {},
collision_mask = {"player-layer"},
selectable_in_game = false,
max_health = 350,
order="b-b-g",
subgroup="enemies",
resistances =
collision_mask = {"player-layer", "object-layer", "water-tile"},
collision_box = {{-0.5, 0}, {0, 32}},
minable = {mining_time = 1, result = "wooden-chest"},
max_health = 100,
corpse = "small-remnants",
fast_replaceable_group = "container",
selection_box = {{-0.5, -0.5}, {0.5, 0.5}},
inventory_size = 16,
open_sound = { filename = "__base__/sound/wooden-chest-open.ogg" },
close_sound = { filename = "__base__/sound/wooden-chest-close.ogg" },
vehicle_impact_sound = { filename = "__base__/sound/car-wood-impact.ogg", volume = 1.0 },
picture =
{
{
type = "physical",
decrease = 2,
percent = 15
},
{
type = "explosion",
decrease = 5,
percent = 15,
},
{
type = "fire",
decrease = 3,
percent = 60,
}
filename = "__base__/graphics/entity/wooden-chest/wooden-chest.png",
priority = "extra-high",
width = 46,
height = 33,
shift = {0.25, 0.015625}
},
working_sound = {
sound =
{
{
filename = "__base__/sound/creatures/spawner.ogg",
volume = 1.0
}
},
apparent_volume = 2
},
dying_sound =
{
{
filename = "__base__/sound/creatures/spawner-death-1.ogg",
volume = 1.0
},
{
filename = "__base__/sound/creatures/spawner-death-2.ogg",
volume = 1.0
}
},
healing_per_tick = 0.02,
collision_box = {{0, -0.5}, {32, 0}},
selection_box = {{0, 0}, {32, 1}},
-- in ticks per 1 pu
pollution_absorbtion_absolute = 20,
pollution_absorbtion_proportional = 0.01,
corpse = "biter-spawner-corpse",
dying_explosion = "blood-explosion-huge",
max_count_of_owned_units = 7,
max_friends_around_to_spawn = 5,
animations =
{
spawner_idle_animation(0, biter_spawner_powered_tint),
spawner_idle_animation(1, biter_spawner_powered_tint),
spawner_idle_animation(2, biter_spawner_powered_tint),
spawner_idle_animation(3, biter_spawner_powered_tint)
},
result_units = (function()
local res = {}
res[1] = {"small-biter", {{0.0, 0.3}, {0.6, 0.0}}}
if not data.is_demo then
-- from evolution_factor 0.3 the weight for medium-biter is linearly rising from 0 to 0.3
-- this means for example that when the evolution_factor is 0.45 the probability of spawning
-- a small biter is 66% while probability for medium biter is 33%.
res[2] = {"medium-biter", {{0.2, 0.0}, {0.6, 0.3}, {0.7, 0.1}}}
-- for evolution factor of 1 the spawning probabilities are: small-biter 0%, medium-biter 1/8, big-biter 4/8, behemoth biter 3/8
res[3] = {"big-biter", {{0.5, 0.0}, {1.0, 0.4}}}
res[4] = {"behemoth-biter", {{0.9, 0.0}, {1.0, 0.3}}}
end
return res
end)(),
-- With zero evolution the spawn rate is 6 seconds, with max evolution it is 2.5 seconds
spawning_cooldown = {360, 150},
spawning_radius = 10,
spawning_spacing = 3,
max_spawn_shift = 0,
max_richness_for_spawn_shift = 100,
call_for_help_radius = 50
circuit_wire_connection_point = circuit_connector_definitions["chest"].points,
circuit_connector_sprites = circuit_connector_definitions["chest"].sprites,
circuit_wire_max_distance = default_circuit_wire_max_distance
},
{
type = "unit-spawner",
name = "chunk-scanner-ns-rampant",
icon = "__base__/graphics/icons/biter-spawner.png",
type = "container",
name = "chunk-scanner-ew-rampant",
icon = "__base__/graphics/icons/wooden-chest.png",
icon_size = 32,
flags = {},
collision_mask = {"player-layer"},
selectable_in_game = false,
max_health = 350,
order="b-b-g",
subgroup="enemies",
resistances =
collision_box = {{0, -0.5}, {32, 0}},
collision_mask = {"player-layer", "object-layer", "water-tile"},
minable = {mining_time = 1, result = "wooden-chest"},
max_health = 100,
corpse = "small-remnants",
fast_replaceable_group = "container",
selection_box = {{-0.5, -0.5}, {0.5, 0.5}},
inventory_size = 16,
open_sound = { filename = "__base__/sound/wooden-chest-open.ogg" },
close_sound = { filename = "__base__/sound/wooden-chest-close.ogg" },
vehicle_impact_sound = { filename = "__base__/sound/car-wood-impact.ogg", volume = 1.0 },
picture =
{
{
type = "physical",
decrease = 2,
percent = 15
},
{
type = "explosion",
decrease = 5,
percent = 15,
},
{
type = "fire",
decrease = 3,
percent = 60,
}
filename = "__base__/graphics/entity/wooden-chest/wooden-chest.png",
priority = "extra-high",
width = 46,
height = 33,
shift = {0.25, 0.015625}
},
working_sound = {
sound =
{
{
filename = "__base__/sound/creatures/spawner.ogg",
volume = 1.0
}
},
apparent_volume = 2
},
dying_sound =
{
{
filename = "__base__/sound/creatures/spawner-death-1.ogg",
volume = 1.0
},
{
filename = "__base__/sound/creatures/spawner-death-2.ogg",
volume = 1.0
}
},
healing_per_tick = 0.02,
collision_box = {{-0.5, 0}, {0, 32}},
selection_box = {{0, 0}, {32, 1}},
-- in ticks per 1 pu
pollution_absorbtion_absolute = 20,
pollution_absorbtion_proportional = 0.01,
corpse = "biter-spawner-corpse",
dying_explosion = "blood-explosion-huge",
max_count_of_owned_units = 7,
max_friends_around_to_spawn = 5,
animations =
{
spawner_idle_animation(0, biter_spawner_powered_tint),
spawner_idle_animation(1, biter_spawner_powered_tint),
spawner_idle_animation(2, biter_spawner_powered_tint),
spawner_idle_animation(3, biter_spawner_powered_tint)
},
result_units = (function()
local res = {}
res[1] = {"small-biter", {{0.0, 0.3}, {0.6, 0.0}}}
if not data.is_demo then
-- from evolution_factor 0.3 the weight for medium-biter is linearly rising from 0 to 0.3
-- this means for example that when the evolution_factor is 0.45 the probability of spawning
-- a small biter is 66% while probability for medium biter is 33%.
res[2] = {"medium-biter", {{0.2, 0.0}, {0.6, 0.3}, {0.7, 0.1}}}
-- for evolution factor of 1 the spawning probabilities are: small-biter 0%, medium-biter 1/8, big-biter 4/8, behemoth biter 3/8
res[3] = {"big-biter", {{0.5, 0.0}, {1.0, 0.4}}}
res[4] = {"behemoth-biter", {{0.9, 0.0}, {1.0, 0.3}}}
end
return res
end)(),
-- With zero evolution the spawn rate is 6 seconds, with max evolution it is 2.5 seconds
spawning_cooldown = {360, 150},
spawning_radius = 10,
spawning_spacing = 3,
max_spawn_shift = 0,
max_richness_for_spawn_shift = 100,
call_for_help_radius = 50
circuit_wire_connection_point = circuit_connector_definitions["chest"].points,
circuit_connector_sprites = circuit_connector_definitions["chest"].sprites,
circuit_wire_max_distance = default_circuit_wire_max_distance
}
})

View File

@ -230,25 +230,6 @@ function tests.baseStats()
end
end
function tests.baseTiles()
local natives = global.natives
for i=1, #natives.bases do
local base = natives.bases[i]
-- local color = "concrete"
-- if (i % 3 == 0) then
-- color = "deepwater"
-- elseif (i % 2 == 0) then
-- color = "water"
-- end
-- for x=1,#base.chunks do
-- local chunk = base.chunks[x]
-- chunkUtils.colorChunk(chunk.pX, chunk.pY, color, game.surfaces[1])
-- end
chunkUtils.colorChunk(base.x, base.y, "deepwater-green", game.surfaces[1])
end
end
function tests.clearBases()
local surface = game.surfaces[1]
@ -284,40 +265,17 @@ function tests.clearBases()
end
end
function tests.colorResourcePoints()
local chunks = global.regionMap.processQueue
for i=1,#chunks do
local chunk = chunks[i]
local color = "concrete"
if (chunk[constants.RESOURCE_GENERATOR] ~= 0) and (chunk[constants.NEST_COUNT] ~= 0) then
color = "hazard-concrete-left"
elseif (chunk[constants.RESOURCE_GENERATOR] ~= 0) then
color = "deepwater"
elseif (chunk[constants.NEST_COUNT] ~= 0) then
color = "water-green"
end
chunkUtils.colorChunk(chunk.x, chunk.y, color, game.surfaces[1])
end
end
function tests.mergeBases()
local natives = global.natives
baseUtils.mergeBases(natives)
end
function tests.showMovementGrid()
function tests.exportAiState()
game.write_file("rampantState.txt", "", false)
local chunks = global.regionMap.processQueue
for i=1,#chunks do
local chunk = chunks[i]
local color = "concrete"
if (chunk[constants.PASSABLE] == constants.CHUNK_ALL_DIRECTIONS) then
color = "hazard-concrete-left"
elseif (chunk[constants.PASSABLE] == constants.CHUNK_NORTH_SOUTH) then
color = "deepwater"
elseif (chunk[constants.PASSABLE] == constants.CHUNK_EAST_WEST) then
color = "water-green"
end
chunkUtils.colorChunk(chunk.x, chunk.y, color, game.surfaces[1])
game.write_file("rampantState.txt", serpent.dump(chunk) .. "\n", true)
end
end

225
visual.rkt Executable file
View File

@ -0,0 +1,225 @@
(module Visualizer racket
(require "parseState.rkt")
(require racket/gui/base)
(require plot)
(define CHUNK_SIZE 32)
(define windowWidth 1024)
(define windowHeight 1024)
(define INVALID_CHUNK (Chunk -1 -1 0 0 0 0 0 0 0))
(define (normalize v low high)
(/ (- v low)
(- high low)))
(define (roundTo x digits)
(* (floor (/ x digits))
digits))
(define (refresh dc)
(drawFrame dc))
(define frameWithEvents% (class frame%
(define/override (on-subwindow-char r event)
(when (eq? (send event get-key-code) #\c)
(exit))
(super on-subwindow-char r event))
(super-new)))
(define (findChunk chunkLookups x y)
(hash-ref chunkLookups (list x y) INVALID_CHUNK))
(define (chunkX->screenX x minX maxX tileWidth)
(roundTo (* (normalize x minX maxX)
windowWidth)
tileWidth))
(define (chunkY->screenY y minY maxY tileHeight)
(roundTo (+ (- windowHeight
(* (normalize y minY maxY)
windowHeight)))
tileHeight))
(define (screenX->chunkX x minX tileWidth)
(+ (* (ceiling (/ x tileWidth))
CHUNK_SIZE)
minX))
(define (screenY->chunkY y maxY tileHeight)
(- maxY
(* (floor (/ y tileHeight))
CHUNK_SIZE)))
(define (displayHighlight dc x y)
;; (print (list x y))
;; (display "\n")
;; (print (list (screenX->chunkX x) (screenY->chunkY y)))
;; (display "\n---\n")
(let ((chunk (findChunk (screenX->chunkX x)
(screenY->chunkY y))))
(set! activeHighlight chunk))
(refresh dc))
(define canvasWithEvents% (class canvas%
(define/override (on-event event)
(match (send event get-event-type)
((== 'motion) (displayChunk (send event get-x) (send event get-y)))
((== 'left-down) (displayHighlight (send event get-x) (send event get-y)))
((== 'right-down) (begin (set! activeHighlight null)
(refresh )
))
(_ #t)))
(super-new)))
(define (newFrame width height x y [label ""])
(new frameWithEvents%
[label label]
[width width]
[height height]
[x x]
[y y]))
(define activeHighlight null)
(define activeLayer "movement")
(define (showVisual aiState windowX windowY windowWidth windowHeight)
(match-let* (((AiState chunks chunkLookups chunkMinMaxes) aiState)
((ChunkRange (MinMax minX maxX)
(MinMax minY maxY)
(MinMax minMovement maxMovement)
(MinMax minBase maxBase)
(MinMax minPlayer maxPlayer)
(MinMax minResource maxResource)
(MinMax minPassable maxPassable)
(MinMax minTick maxTick)
(MinMax minRating maxRating)) chunkMinMaxes))
;; (pretty-display aiState)
;; (display "\n")
(define templates (list '(250 500 0 0 "controls")
(list windowWidth windowHeight windowX windowY "map")))
(define frames (map (lambda (frame)
(match-let (((list width height x y name) frame))
(newFrame width height x y name)))
templates))
(define mainFrame (first frames))
(define mapFrame (second frames))
(define canvass (map (lambda (frame)
(let ((c (new canvasWithEvents%
[parent frame]
[paint-callback (lambda (canvas dc)
(drawFrame dc))])))
(send c set-canvas-background (make-object color% 0 0 0))
c))
(cdr frames)))
(define dcs (map (lambda (canvas)
(send canvas get-dc))
canvass))
(define dcMap (first dcs))
(define tileWidth (ceiling (/ windowWidth (abs (/ (- maxX minX) CHUNK_SIZE)))))
(define tileHeight (ceiling (/ windowHeight (+ (abs (/ (- maxY minY) CHUNK_SIZE)) 1))))
;; (print (list (exact->inexact tileWidth)
;; (exact->inexact tileHeight)))
;; (display "\n")
(define (drawFrame context)
(send context suspend-flush)
(let ((chunkField (eval (string->symbol (string-append "Chunk-" activeLayer))))
(chunkRangeField (eval (string->symbol (string-append "ChunkRange-" activeLayer)))))
(map (lambda (chunk)
(let ((x (chunkX->screenX (Chunk-x chunk)))
(y (chunkY->screenY (Chunk-y chunk))))
(if (eq? activeHighlight chunk)
(send context set-pen (make-object color% 255 255 255) 1 'solid)
(send context set-pen (make-object color% 0 0 0) 1 'solid))
(define (dcDraw dc property minMax)
(scaleColor dc property (MinMax-min minMax) (MinMax-max minMax))
(send dc draw-rectangle x y tileWidth tileHeight))
(dcDraw context
(chunkField chunk)
(chunkRangeField chunkMinMaxes))))
chunks))
(send context resume-flush))
(define (displayChunk x y)
(send siteBox set-label
(chunk->string (findChunk (screenX->chunkX x)
(screenY->chunkY y)))))
(define (scaleColor dc value low high)
(define v (/ (- value low)
(- high low)))
(define r (if (= v 0)
0
(if (> 0.75 v)
150
(if (> 0.50 v)
100
50))))
(define g (inexact->exact (round (* v 255))))
(send dc set-brush (make-object color% r g 0) 'solid))
(define panel (new panel%
[parent mainFrame]
(alignment '(left top))))
(define statusBox (new message%
[parent panel]
[label (~v "")]
[vert-margin 16]))
(define siteBox (new message%
[parent panel]
[label ""]
[vert-margin 30]))
(new radio-box%
[label "Show Layer"]
[choices (list "movement" "base" "player" "resource" "passable" "tick" "rating")]
[selection 0]
[parent mainFrame]
(callback (lambda (radioButton event)
(set! activeLayer (send radioButton get-item-label (send radioButton get-selection)))
(refresh))))
(new button%
[parent mainFrame]
[label "Refresh"]
(callback (lambda (button event)
(exit))))
(new button%
[parent mainFrame]
[label "Quit"]
(callback (lambda (button event)
(exit))))
(map (lambda (f)
(send f show #t))
frames)
'showing))
(define (test)
(showVisual (readState "/data/games/factorio/script-output/rampantState.txt")
500
0
1024
1024)))