Created
January 24, 2017 16:55
-
-
Save bonzaiferroni/ab73313109bdd1360344312e933bbbe9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {WorldMap} from "./WorldMap"; | |
export class TradeNetwork { | |
public storages: StructureStorage[] = []; | |
public terminals: StructureTerminal[] = []; | |
private shortages: {[resourceType: string]: StructureTerminal[] } = {}; | |
private surpluses: {[resourceType: string]: StructureTerminal[] } = {}; | |
private _inventory: {[key: string]: number}; | |
private map: WorldMap; | |
private alreadyTraded: {[roomName: string]: boolean } = {}; | |
private memory: { | |
tradeRoomIndex: number; | |
}; | |
constructor(map: WorldMap) { | |
this.map = map; | |
if (!Memory.empire) { Memory.empire = {}; } | |
this.memory = Memory.empire; | |
} | |
init() { | |
this.registerMyRooms(); | |
this.registerPartnerRooms(); | |
} | |
public actions() { | |
this.observeTradeRoom(); | |
this.tradeMonkey(); | |
this.reportTransactions(); | |
} | |
// should only be accessed after Init() | |
get inventory(): {[key: string]: number} { | |
if (!this._inventory) { | |
let inventory: {[key: string]: number } = {}; | |
for (let terminal of this.terminals) { | |
for (let mineralType in terminal.store) { | |
if (!terminal.store.hasOwnProperty(mineralType)) continue; | |
if (inventory[mineralType] === undefined) { | |
inventory[mineralType] = 0; | |
} | |
inventory[mineralType] += terminal.store[mineralType]; | |
} | |
} | |
// gather mineral/storage data | |
for (let storage of this.storages) { | |
for (let mineralType in storage.store) { | |
if (inventory[mineralType] === undefined) { | |
inventory[mineralType] = 0; | |
} | |
inventory[mineralType] += storage.store[mineralType]; | |
} | |
} | |
this._inventory = inventory; | |
} | |
return this._inventory; | |
} | |
private observeTradeRoom() { | |
let tradeRoomNames = Object.keys(this.map.tradeMap); | |
if (tradeRoomNames.length === 0) { return; } | |
let count = 0; | |
while (count < tradeRoomNames.length) { | |
if (this.memory.tradeRoomIndex === undefined || this.memory.tradeRoomIndex >= tradeRoomNames.length) { | |
this.memory.tradeRoomIndex = 0; | |
} | |
let roomName = tradeRoomNames[this.memory.tradeRoomIndex++]; | |
let room = Game.rooms[roomName]; | |
if (room) { | |
count++; | |
continue; | |
} | |
let roomMemory = this.map.tradeMap[roomName]; | |
if (Game.time < roomMemory.nextTrade) { | |
count++; | |
continue; | |
} | |
let observer = this.findObserver(roomName); | |
if (!observer) { | |
roomMemory.nextTrade = Game.time + 10000; | |
count++; | |
continue; | |
} | |
observer.observeRoom(roomName, "tradeNetwork"); | |
break; | |
} | |
} | |
private findObserver(observedRoomName: string): StructureObserver { | |
for (let observingRoomName in this.map.controlledRooms) { | |
if (Game.map.getRoomLinearDistance(observedRoomName, observingRoomName) > 10) { continue; } | |
let room = this.map.controlledRooms[observingRoomName]; | |
if (room.controller.level < 8) { continue; } | |
let observer = _(room.find<StructureObserver>(FIND_STRUCTURES)) | |
.filter(s => s.structureType === STRUCTURE_OBSERVER) | |
.head(); | |
if (observer) { return observer; } | |
} | |
} | |
/** | |
* Used to determine whether there is an abundance of a given resource type among all terminals. | |
* Should only be used after init() phase | |
* @param resourceType | |
* @param amountPerRoom - specify how much per missionRoom you consider an abundance, default value is SURPLUS_AMOUNT | |
*/ | |
public hasAbundance(resourceType: string, amountPerRoom = RESERVE_AMOUNT * 2) { | |
let abundanceAmount = this.terminals.length * amountPerRoom; | |
return this.inventory[resourceType] && this.inventory[resourceType] > abundanceAmount; | |
} | |
private registerMyRooms() { | |
for (let roomName in this.map.controlledRooms) { | |
let room = this.map.controlledRooms[roomName]; | |
if (room.terminal && room.terminal.my && room.controller.level >= 6) { | |
this.terminals.push(room.terminal); | |
} | |
if (room.storage && room.storage.my && room.controller.level >= 4) { | |
this.storages.push(room.storage); | |
} | |
if (TradeNetwork.canTrade(room)) { | |
this.analyzeResources(room); | |
} | |
} | |
} | |
private registerPartnerRooms() { | |
for (let room of this.map.tradeRooms) { | |
if (TradeNetwork.canTrade(room)) { | |
this.analyzeResources(room); | |
} | |
else { | |
delete room.memory.nextTrade; | |
} | |
} | |
} | |
public analyzeResources(room: Room) { | |
let tradeResourceTypes = TRADE_WITH_SELF; | |
if (!room.controller.my) { | |
tradeResourceTypes = TRADE_WITH_PARTNERS; | |
} | |
let terminalFull = _.sum(room.terminal.store) > 270000; | |
for (let resourceType of tradeResourceTypes) { | |
if (resourceType === RESOURCE_ENERGY) { | |
if (room.terminal.store.energy < 50000 && room.storage.store.energy < NEED_ENERGY_THRESHOLD | |
&& !terminalFull) { | |
this.registerShortage(resourceType, room.terminal); | |
} | |
else if (room.storage.store.energy > SUPPLY_ENERGY_THRESHOLD) { | |
this.registerSurplus(resourceType, room.terminal); | |
} | |
} | |
else { | |
let amount = room.terminal.store[resourceType] || 0; | |
if (amount < RESERVE_AMOUNT && !terminalFull) { | |
this.registerShortage(resourceType, room.terminal); | |
} | |
else if (amount >= SURPLUS_AMOUNT) { | |
this.registerSurplus(resourceType, room.terminal); | |
} | |
} | |
} | |
} | |
private registerShortage(resourceType: string, terminal: StructureTerminal) { | |
if (!this.shortages[resourceType]) { this.shortages[resourceType] = []} | |
this.shortages[resourceType].push(terminal); | |
} | |
private registerSurplus(resourceType: string, terminal: StructureTerminal) { | |
if (!terminal.my) { return; } // we could erase this if we were all running the same code | |
if (!this.surpluses[resourceType]) { this.surpluses[resourceType] = []} | |
this.surpluses[resourceType].push(terminal); | |
} | |
// connects shortages and surpluses | |
private tradeMonkey() { | |
for (let resourceType in this.shortages) { | |
let shortageTerminals = this.shortages[resourceType]; | |
let surplusTerminals = this.surpluses[resourceType]; | |
if (!surplusTerminals) { continue; } | |
for (let shortage of shortageTerminals) { | |
let bestSurplus; | |
let bestDistance = Number.MAX_VALUE; | |
for (let surplus of surplusTerminals) { | |
let distance = Game.map.getRoomLinearDistance(shortage.room.name, surplus.room.name); | |
if (distance > bestDistance) { continue; } | |
if (distance > this.acceptableDistance(resourceType, surplus)) { continue; } | |
bestDistance = distance; | |
bestSurplus = surplus; | |
} | |
if (bestSurplus && !this.alreadyTraded[bestSurplus.room.name]) { | |
let requiredEnergy = 10000; | |
if (resourceType === RESOURCE_ENERGY) { | |
requiredEnergy = 30000; | |
} | |
if (bestSurplus.store.energy >= requiredEnergy) { | |
let amount = this.sendAmount(resourceType, shortage); | |
this.sendResource(bestSurplus, resourceType, amount, shortage); | |
} | |
this.alreadyTraded[bestSurplus.room.name] = true; | |
} | |
} | |
} | |
} | |
private sendAmount(resourceType: string, shortage: StructureTerminal): number { | |
if (resourceType === RESOURCE_ENERGY) { return TRADE_ENERGY_AMOUNT; } | |
let amountStored = shortage.store[resourceType] || 0; | |
return RESERVE_AMOUNT - amountStored; | |
} | |
private acceptableDistance(resourceType: string, surplus: StructureTerminal): number { | |
if (IGNORE_TRADE_DISTANCE[resourceType]) { return Number.MAX_VALUE; } | |
else if (resourceType === RESOURCE_ENERGY) { | |
if (_.sum(surplus.room.storage.store) >= 950000) { | |
return Number.MAX_VALUE; | |
} else { | |
return TRADE_DISTANCE; | |
} | |
} else { | |
if (surplus.room.storage.store[resourceType] > 80000) { | |
return Number.MAX_VALUE; | |
} else { | |
return TRADE_DISTANCE; | |
} | |
} | |
} | |
private sendResource(localTerminal: StructureTerminal, resourceType: string, amount: number, otherTerminal: StructureTerminal) { | |
if (amount < 100) { | |
amount = 100; | |
} | |
let outcome = localTerminal.send(resourceType, amount, otherTerminal.room.name); | |
if (outcome !== OK) { | |
console.log(`NETWORK: error sending resource in ${localTerminal.room.name}, outcome: ${outcome}`); | |
console.log(`arguments used: ${resourceType}, ${amount}, ${otherTerminal.room.name}`); | |
} | |
} | |
public static canTrade(room: Room) { | |
return room.controller && room.controller.level >= 6 && room.storage && room.terminal | |
&& (!room.controller.sign || room.controller.sign.text !== "noTrade"); | |
} | |
reportTransactions() { | |
if (Game.time % 10 !== 0) return; | |
let kFormatter = (num: number) => { | |
return num > 999 ? (num/1000).toFixed(1) + 'k' : num | |
}; | |
let consoleReport = (item: Transaction) => { | |
let distance = Game.map.getRoomLinearDistance(item.from, item.to); | |
let cost = Game.market.calcTransactionCost(item.amount, item.from, item.to); | |
console.log( | |
`TRADE: ${_.padLeft(`${item.from} ${item.sender ? item.sender.username : "npc"}`.substr(0, 12), 12)} ` + | |
`→ ${_.pad(`${kFormatter(item.amount)} ${item.resourceType}`.substr(0, 12), 12)} → ` + | |
`${_.padRight(`${item.to} ${item.recipient ? item.recipient.username : "npc"}`.substr(0, 12), 12)} ` + | |
`(dist: ${distance}, cost: ${kFormatter(cost)})` | |
); | |
}; | |
for (let item of Game.market.incomingTransactions) { | |
if (!item.sender) continue; | |
if (item.time >= Game.time - 10) { | |
let username = item.sender.username; | |
if (!username) { username = "npc"; } | |
if (!Memory.traders[username]) { Memory.traders[username] = {}; } | |
if (Memory.traders[username][item.resourceType] === undefined) { | |
Memory.traders[username][item.resourceType] = 0; | |
} | |
Memory.traders[username][item.resourceType] += item.amount; | |
consoleReport(item); | |
this.processTransaction(item); | |
} | |
else { | |
break; | |
} | |
} | |
for (let item of Game.market.outgoingTransactions) { | |
if (!item.recipient) continue; | |
if (item.time >= Game.time - 10) { | |
let username = item.recipient.username; | |
if (!username) { username = "npc"; } | |
if (!Memory.traders[username]) { Memory.traders[username] = {}; } | |
if (Memory.traders[username][item.resourceType] === undefined) { | |
Memory.traders[username][item.resourceType] = 0; | |
} | |
Memory.traders[item.recipient.username][item.resourceType] -= item.amount; | |
if (item.recipient.username === this.terminals[0].owner.username) { continue; } | |
consoleReport(item); | |
} | |
else { | |
break; | |
} | |
} | |
} | |
protected processTransaction(item: Transaction) { } // overridden in BonzaiNetwork | |
} | |
// these are the constants that govern your energy balance | |
// rooms below this will try to pull energy... | |
export const NEED_ENERGY_THRESHOLD = 200000; | |
// ...from rooms above this. | |
export const SUPPLY_ENERGY_THRESHOLD = 250000; | |
// rooms above this will start processing power | |
export const POWER_PROCESS_THRESHOLD = 350000; | |
// rooms above this will spawn a more powerful wall-builder to try to sink energy that way | |
export const ENERGYSINK_THRESHOLD = 450000; | |
export const RESERVE_AMOUNT = 5000; | |
export const SURPLUS_AMOUNT = 10000; | |
export const TRADE_DISTANCE = 6; | |
export const IGNORE_TRADE_DISTANCE = { | |
["XUH2O"]: true, | |
["XLHO2"]: true, | |
[RESOURCE_POWER]: true, | |
}; | |
export const MINERALS_RAW = ["H", "O", "Z", "U", "K", "L", "X"]; | |
export const PRODUCT_LIST = ["XUH2O", "XLHO2", "XLH2O", "XKHO2", "XGHO2", "XZHO2", "XZH2O", "G", "XGH2O"]; | |
export const TRADE_WITH_SELF = [RESOURCE_ENERGY].concat(PRODUCT_LIST).concat(MINERALS_RAW).concat(RESOURCE_POWER); | |
export const TRADE_WITH_PARTNERS = [RESOURCE_ENERGY].concat(PRODUCT_LIST).concat(MINERALS_RAW); | |
export const TRADE_ENERGY_AMOUNT = 10000; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment