├── .gitignore ├── LICENSE.txt ├── README.md ├── bazaarbot ├── Agent.hx ├── Economy.hx ├── Good.hx ├── Market.hx ├── MarketData.hx ├── Offer.hx ├── agent │ ├── BasicAgent.hx │ ├── Inventory.hx │ ├── InventoryData.hx │ ├── Logic.hx │ ├── LogicHScript.hx │ └── LogicScript.hx └── utils │ ├── ArrayUtil.hx │ ├── DestroyUtil.hx │ ├── EconNoun.hx │ ├── History.hx │ ├── HistoryLog.hx │ ├── MarketReport.hx │ ├── Quick.hx │ ├── Signal.hx │ └── TradeBook.hx ├── examples └── doran_and_parberry │ ├── Assets │ ├── nme.png │ ├── nme.svg │ ├── scripts │ │ ├── best_job.hs │ │ ├── blacksmith.hs │ │ ├── farmer.hs │ │ ├── miner.hs │ │ ├── refiner.hs │ │ └── woodcutter.hs │ └── settings.json │ ├── example.hxproj │ ├── project.xml │ └── source │ ├── DoranAndParberryEconomy.hx │ ├── Main.hx │ ├── MarketDisplay.hx │ └── jobs │ ├── LogicBlacksmith.hx │ ├── LogicFarmer.hx │ ├── LogicGeneric.hx │ ├── LogicMiner.hx │ ├── LogicRefiner.hx │ └── LogicWoodcutter.hx └── haxelib.json /.gitignore: -------------------------------------------------------------------------------- 1 | Docs/LARC-2010-03.pdf 2 | Export/* 3 | examples/doran_and_parberry/Export/ 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Lars A. Doucet 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bazaarBot 2 | ========= 3 | 4 | A simple agent-based free market simulator engine. 5 | 6 | This engine consists of various "Agents" trading commodities, with emergent free-floating prices that rise and fall according to the laws of supply and demand. 7 | 8 | The eventual goal is to create an open-source "Economics engine" for games and simulations, much like contemporary open-source "Physics engines." 9 | 10 | Based on "[Emergent Economies for Role Playing Games](http://larc.unt.edu/techreports/LARC-2010-03.pdf)" by Jonathon Doran and Ian Parberry. 11 | 12 | Source: [Procedural Content Generation](http://larc.unt.edu/ian/research/content/) 13 | 14 | Building the example project 15 | --------------------------- 16 | 17 | 1. Read this: [Getting Started with OpenFL](http://www.openfl.org/documentation/setup/) 18 | 2. Install Haxe and OpenFL and everything according to the above instructions 19 | 3. Install the `hscript` library (a dependency of bazaarBot): `haxelib install hscript` 20 | 4. Clone this repo somewhere on your hard-drive, let's call that `path/to/bazaarbot` 21 | 5. On the command line type `haxelib dev bazaarbot path/to/bazaarbot` to add bazaarbot as a Haxe library. 22 | 6. Open a command-line, navigate to `path/to/bazaarbot/examples/doran_and_parberry` 23 | 7. Run `lime build flash` to compile for flash 24 | 8. Run `lime build windows` to compile for cpp/windows (or `lime build mac` or `lime build linux`, etc) 25 | 9. Run `lime build html5` to compile for html5 26 | 10. Binary executables will appear in the `Export/` folder 27 | 11. Substitute `lime test` instead of `lime build` if you want to build AND immediately run the result. 28 | 29 | -------------------------------------------------------------------------------- /bazaarbot/Agent.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot; 2 | 3 | import bazaarbot.agent.BasicAgent; 4 | import bazaarbot.agent.BasicAgent.AgentData; 5 | import bazaarbot.Market; 6 | import bazaarbot.Offer; 7 | import flash.geom.Point; 8 | 9 | /** 10 | * An agent that performs the basic logic from the Doran & Parberry article 11 | * @author 12 | */ 13 | class Agent extends BasicAgent 14 | { 15 | public static inline var SIGNIFICANT:Float = 0.25; //25% more or less is "significant" 16 | public static inline var SIG_IMBALANCE:Float = 0.33; 17 | public static inline var LOW_INVENTORY:Float = 0.1; //10% of ideal inventory = "LOW" 18 | public static inline var HIGH_INVENTORY:Float = 2.0; //200% of ideal inventory = "HIGH" 19 | 20 | public static inline var MIN_PRICE:Float = 0.01; //lowest possible price 21 | 22 | public function new(id:Int, data:AgentData) 23 | { 24 | super(id, data); 25 | } 26 | 27 | override public function createBid(bazaar:Market, good:String, limit:Float):Offer 28 | { 29 | var bidPrice:Float = determinePriceOf(good); 30 | var ideal:Float = determinePurchaseQuantity(bazaar, good); 31 | 32 | //can't buy more than limit 33 | var quantityToBuy:Float = ideal > limit ? limit : ideal; 34 | if (quantityToBuy > 0) 35 | { 36 | return new Offer(id, good, quantityToBuy, bidPrice); 37 | } 38 | return null; 39 | } 40 | 41 | override public function createAsk(bazaar:Market, commodity_:String, limit_:Float):Offer 42 | { 43 | var ask_price:Float = determinePriceOf(commodity_); 44 | var ideal:Float = determineSaleQuantity(bazaar, commodity_); 45 | 46 | //can't sell less than limit 47 | var quantity_to_sell:Float = ideal < limit_ ? limit_ : ideal; 48 | if (quantity_to_sell > 0) 49 | { 50 | return new Offer(id, commodity_, quantity_to_sell, ask_price); 51 | } 52 | return null; 53 | } 54 | 55 | override public function generateOffers(bazaar:Market, commodity:String):Void 56 | { 57 | var offer:Offer; 58 | var surplus:Float = _inventory.surplus(commodity); 59 | if (surplus >= 1) 60 | { 61 | offer = createAsk(bazaar, commodity, 1); 62 | if (offer != null) 63 | { 64 | bazaar.ask(offer); 65 | } 66 | } 67 | else 68 | { 69 | var shortage:Float = _inventory.shortage(commodity); 70 | var space:Float = _inventory.getEmptySpace(); 71 | var unit_size:Float = _inventory.getCapacityFor(commodity); 72 | 73 | if (shortage > 0 && space >= unit_size) 74 | { 75 | var limit:Float = 0; 76 | if ((shortage * unit_size) <= space) //enough space for ideal order 77 | { 78 | limit = shortage; 79 | } 80 | else //not enough space for ideal order 81 | { 82 | limit = Math.floor(space / shortage); 83 | } 84 | 85 | if (limit > 0) 86 | { 87 | offer = createBid(bazaar, commodity, limit); 88 | if (offer != null) 89 | { 90 | bazaar.bid(offer); 91 | } 92 | } 93 | } 94 | } 95 | } 96 | 97 | override public function updatePriceModel(bazaar:Market, act:String, good:String, success:Bool, unitPrice:Float = 0):Void 98 | { 99 | var observed_trades:Array; 100 | 101 | if (success) 102 | { 103 | //Add this to my list of observed trades 104 | observed_trades = _observedTradingRange.get(good); 105 | observed_trades.push(unitPrice); 106 | } 107 | 108 | var public_mean_price:Float = bazaar.getAverageHistoricalPrice(good, 1); 109 | 110 | var belief:Point = getPriceBelief(good); 111 | var mean:Float = (belief.x + belief.y) / 2; 112 | var wobble:Float = 0.05; 113 | 114 | var delta_to_mean:Float = mean - public_mean_price; 115 | 116 | if (success) 117 | { 118 | if (act == "buy" && delta_to_mean > SIGNIFICANT) //overpaid 119 | { 120 | belief.x -= delta_to_mean / 2; //SHIFT towards mean 121 | belief.y -= delta_to_mean / 2; 122 | } 123 | else if (act == "sell" && delta_to_mean < -SIGNIFICANT) //undersold 124 | { 125 | belief.x -= delta_to_mean / 2; //SHIFT towards mean 126 | belief.y -= delta_to_mean / 2; 127 | } 128 | 129 | belief.x += wobble * mean; //increase the belief's certainty 130 | belief.y -= wobble * mean; 131 | } 132 | else 133 | { 134 | belief.x -= delta_to_mean / 2; //SHIFT towards the mean 135 | belief.y -= delta_to_mean / 2; 136 | 137 | var special_case:Bool = false; 138 | var stocks:Float = queryInventory(good); 139 | var ideal:Float = _inventory.ideal(good); 140 | 141 | if (act == "buy" && stocks < LOW_INVENTORY * ideal) 142 | { 143 | //very low on inventory AND can't buy 144 | wobble *= 2; //bid more liberally 145 | special_case = true; 146 | } 147 | else if (act == "sell" && stocks > HIGH_INVENTORY * ideal) 148 | { 149 | //very high on inventory AND can't sell 150 | wobble *= 2; //ask more liberally 151 | special_case = true; 152 | } 153 | 154 | if (!special_case) 155 | { 156 | //Don't know what else to do? Check supply vs. demand 157 | var asks:Float = bazaar.history.asks.average(good,1); 158 | var bids:Float = bazaar.history.bids.average(good,1); 159 | 160 | //supply_vs_demand: 0=balance, 1=all supply, -1=all demand 161 | var supply_vs_demand:Float = (asks - bids) / (asks + bids); 162 | 163 | //too much supply, or too much demand 164 | if (supply_vs_demand > SIG_IMBALANCE || supply_vs_demand < -SIG_IMBALANCE) 165 | { 166 | //too much supply: lower price 167 | //too much demand: raise price 168 | 169 | var new_mean = public_mean_price * (1 - supply_vs_demand); 170 | delta_to_mean = mean - new_mean; 171 | 172 | belief.x -= delta_to_mean / 2; //SHIFT towards anticipated new mean 173 | belief.y -= delta_to_mean / 2; 174 | } 175 | } 176 | 177 | belief.x -= wobble * mean; //decrease the belief's certainty 178 | belief.y += wobble * mean; 179 | } 180 | 181 | if (belief.x < MIN_PRICE) 182 | { 183 | belief.x = MIN_PRICE; 184 | } 185 | else if (belief.y < MIN_PRICE) 186 | { 187 | belief.y = MIN_PRICE; 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /bazaarbot/Economy.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot; 2 | import bazaarbot.agent.BasicAgent; 3 | 4 | /** 5 | * ... 6 | * @author larsiusprime 7 | */ 8 | 9 | class Economy 10 | { 11 | public function new() 12 | { 13 | _markets = []; 14 | } 15 | 16 | public function addMarket(m:Market) 17 | { 18 | if (_markets.indexOf(m) == -1) 19 | { 20 | _markets.push(m); 21 | m.signalBankrupt.add(onBankruptcy); 22 | } 23 | } 24 | 25 | public function getMarket(name:String):Market 26 | { 27 | for (m in _markets) 28 | { 29 | if (m.name == name) return m; 30 | } 31 | return null; 32 | } 33 | 34 | public function simulate(rounds:Int) 35 | { 36 | for (m in _markets) 37 | { 38 | m.simulate(rounds); 39 | } 40 | } 41 | 42 | /***PRIVATE***/ 43 | 44 | private function onBankruptcy(m:Market, a:BasicAgent):Void 45 | { 46 | //no implemenation -- provide your own in a subclass 47 | } 48 | 49 | private var _markets:Array; 50 | } 51 | -------------------------------------------------------------------------------- /bazaarbot/Good.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot; 2 | 3 | /** 4 | * ... 5 | * @author 6 | */ 7 | 8 | class Good 9 | { 10 | public var id:String = ""; //string id of good 11 | public var size:Float = 1.0; //inventory size taken up 12 | 13 | public function new(id_:String,size_:Float) 14 | { 15 | id = id_; 16 | size = size_; 17 | } 18 | 19 | public function copy():Good 20 | { 21 | return new Good(id, size); 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /bazaarbot/Market.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot; 2 | import bazaarbot.agent.BasicAgent; 3 | import bazaarbot.agent.BasicAgent.AgentData; 4 | import bazaarbot.agent.Logic; 5 | import bazaarbot.utils.History; 6 | import bazaarbot.utils.MarketReport; 7 | import bazaarbot.utils.Quick; 8 | import bazaarbot.utils.Signal.TypedSignal; 9 | import bazaarbot.utils.TradeBook; 10 | import haxe.Json; 11 | import haxe.xml.Fast; 12 | import flash.errors.Error; 13 | import hscript.Interp; 14 | import hscript.Parser; 15 | import openfl.Assets; 16 | 17 | /** 18 | * ... 19 | * @author 20 | */ 21 | class Market 22 | { 23 | public var name:String; 24 | 25 | /**Logs information about all economic activity in this market**/ 26 | public var history:History; 27 | 28 | /**Signal fired when an agent's money reaches 0 or below**/ 29 | public var signalBankrupt:TypedSignalBasicAgent->Void>; 30 | 31 | public function new(name:String) 32 | { 33 | this.name = name; 34 | 35 | history = new History(); 36 | _book = new TradeBook(); 37 | _goodTypes = new Array(); 38 | _agents = new Array(); 39 | _mapGoods = new Map(); 40 | _mapAgents = new Map(); 41 | 42 | signalBankrupt = new TypedSignalBasicAgent->Void>(); 43 | } 44 | 45 | public function init(data:MarketData):Void 46 | { 47 | fromData(data); 48 | } 49 | 50 | public function numTypesOfGood():Int 51 | { 52 | return _goodTypes.length; 53 | } 54 | 55 | public function numAgents():Int 56 | { 57 | return _agents.length; 58 | } 59 | 60 | public function replaceAgent(oldAgent:BasicAgent, newAgent:BasicAgent):Void 61 | { 62 | newAgent.id = oldAgent.id; 63 | _agents[oldAgent.id] = newAgent; 64 | oldAgent.destroy(); 65 | newAgent.init(this); 66 | } 67 | 68 | @:access(bazaarbot.agent.BasicAgent) 69 | public function simulate(rounds:Int):Void 70 | { 71 | for (round in 0...rounds) 72 | { 73 | for (agent in _agents) 74 | { 75 | agent.moneyLastRound = agent.money; 76 | agent.simulate(this); 77 | 78 | for (commodity in _goodTypes) 79 | { 80 | agent.generateOffers(this, commodity); 81 | } 82 | } 83 | 84 | for (commodity in _goodTypes) 85 | { 86 | resolveOffers(commodity); 87 | } 88 | for (agent in _agents) 89 | { 90 | if (agent.money <= 0) 91 | { 92 | signalBankrupt.dispatch(this, agent); 93 | } 94 | } 95 | _roundNum++; 96 | } 97 | } 98 | 99 | public function ask(offer:Offer):Void 100 | { 101 | _book.ask(offer); 102 | } 103 | 104 | public function bid(offer:Offer):Void 105 | { 106 | _book.bid(offer); 107 | } 108 | 109 | /** 110 | * Returns the historical mean price of the given commodity over the last X rounds 111 | * @param commodity_ string id of commodity 112 | * @param range number of rounds to look back 113 | * @return 114 | */ 115 | 116 | public function getAverageHistoricalPrice(good:String, range:Int):Float 117 | { 118 | return history.prices.average(good, range); 119 | } 120 | 121 | /** 122 | * Get the good with the highest demand/supply ratio over time 123 | * @param minimum the minimum demand/supply ratio to consider an opportunity 124 | * @param range number of rounds to look back 125 | * @return 126 | */ 127 | 128 | public function getHottestGood(minimum:Float = 1.5, range:Int = 10):String 129 | { 130 | var best_market:String = ""; 131 | var best_ratio:Float = Math.NEGATIVE_INFINITY; 132 | for (good in _goodTypes) 133 | { 134 | var asks:Float = history.asks.average(good, range); 135 | var bids:Float = history.bids.average(good, range); 136 | 137 | var ratio:Float = 0; 138 | if (asks == 0 && bids > 0) 139 | { 140 | //If there are NONE on the market we artificially create a fake supply of 1/2 a unit to avoid the 141 | //crazy bias that "infinite" demand can cause... 142 | 143 | asks = 0.5; 144 | } 145 | 146 | ratio = bids / asks; 147 | 148 | if (ratio > minimum && ratio > best_ratio) 149 | { 150 | best_ratio = ratio; 151 | best_market = good; 152 | } 153 | } 154 | return best_market; 155 | } 156 | 157 | /** 158 | * Returns the good that has the lowest average price over the given range of time 159 | * @param range how many rounds to look back 160 | * @param exclude goods to exclude 161 | * @return 162 | */ 163 | 164 | public function getCheapestGood(range:Int, exclude:Array = null):String 165 | { 166 | var best_price:Float = Math.POSITIVE_INFINITY; 167 | var best_good:String = ""; 168 | for (g in _goodTypes) 169 | { 170 | if (exclude == null || exclude.indexOf(g) == -1) 171 | { 172 | var price:Float = history.prices.average(g, range); 173 | if (price < best_price) 174 | { 175 | best_price = price; 176 | best_good = g; 177 | } 178 | } 179 | } 180 | return best_good; 181 | } 182 | 183 | /** 184 | * Returns the good that has the highest average price over the given range of time 185 | * @param range how many rounds to look back 186 | * @param exclude goods to exclude 187 | * @return 188 | */ 189 | 190 | public function getDearestGood(range:Int, exclude:Array = null):String 191 | { 192 | var best_price:Float = 0; 193 | var best_good:String = ""; 194 | for (g in _goodTypes) 195 | { 196 | if (exclude == null || exclude.indexOf(g) == -1) 197 | { 198 | var price = history.prices.average(g, range); 199 | if (price > best_price) 200 | { 201 | best_price = price; 202 | best_good = g; 203 | } 204 | } 205 | } 206 | return best_good; 207 | } 208 | 209 | /** 210 | * 211 | * @param range 212 | * @return 213 | */ 214 | public function getMostProfitableAgentClass(range:Int = 10):String 215 | { 216 | var best:Float = Math.NEGATIVE_INFINITY; 217 | var bestClass:String=""; 218 | for (className in _mapAgents.keys()) 219 | { 220 | var val:Float = history.profit.average(className, range); 221 | if (val > best) 222 | { 223 | bestClass = className; 224 | best = val; 225 | } 226 | } 227 | return bestClass; 228 | } 229 | 230 | public function getAgentClass(className:String):AgentData 231 | { 232 | return _mapAgents.get(className); 233 | } 234 | 235 | public function getAgentClassNames():Array 236 | { 237 | var agentData = []; 238 | for (key in _mapAgents.keys()) 239 | { 240 | agentData.push(key); 241 | } 242 | return agentData; 243 | } 244 | 245 | public function getGoods():Array 246 | { 247 | return _goodTypes.copy(); 248 | } 249 | 250 | public function getGoods_unsafe():Array 251 | { 252 | return _goodTypes; 253 | } 254 | 255 | public function getGoodEntry(str:String):Good 256 | { 257 | if (_mapGoods.exists(str)) 258 | { 259 | return _mapGoods.get(str).copy(); 260 | } 261 | return null; 262 | } 263 | 264 | /********REPORT**********/ 265 | public function get_marketReport(rounds:Int):MarketReport 266 | { 267 | var mr:MarketReport = new MarketReport(); 268 | mr.strListGood = "Commodities\n\n"; 269 | mr.strListGoodPrices = "Price\n\n"; 270 | mr.strListGoodTrades = "Trades\n\n"; 271 | mr.strListGoodAsks = "Supply\n\n"; 272 | mr.strListGoodBids = "Demand\n\n"; 273 | 274 | mr.strListAgent = "Classes\n\n"; 275 | mr.strListAgentCount = "Count\n\n"; 276 | mr.strListAgentProfit = "Profit\n\n"; 277 | mr.strListAgentMoney = "Money\n\n"; 278 | 279 | mr.arrStrListInventory = []; 280 | 281 | for (commodity in _goodTypes) 282 | { 283 | mr.strListGood += commodity + "\n"; 284 | 285 | var price:Float = history.prices.average(commodity, rounds); 286 | mr.strListGoodPrices += Quick.numStr(price, 2) + "\n"; 287 | 288 | var asks:Float = history.asks.average(commodity, rounds); 289 | mr.strListGoodAsks += Std.int(asks) + "\n"; 290 | 291 | var bids:Float = history.bids.average(commodity, rounds); 292 | mr.strListGoodBids += Std.int(bids) + "\n"; 293 | 294 | var trades:Float = history.trades.average(commodity, rounds); 295 | mr.strListGoodTrades += Std.int(trades) + "\n"; 296 | 297 | mr.arrStrListInventory.push(commodity + "\n\n"); 298 | } 299 | for (key in _mapAgents.keys()) 300 | { 301 | var inventory:Array = []; 302 | for (str in _goodTypes) 303 | { 304 | inventory.push(0); 305 | } 306 | mr.strListAgent += key + "\n"; 307 | var profit:Float = history.profit.average(key, rounds); 308 | mr.strListAgentProfit += Quick.numStr(profit, 2) + "\n"; 309 | 310 | var test_profit:Float = 0; 311 | var list = _agents.filter(function(a:BasicAgent):Bool { return a.className == key; } ); 312 | var count:Int = list.length; 313 | var money:Float = 0; 314 | 315 | for (a in list) 316 | { 317 | money += a.money; 318 | for (lic in 0..._goodTypes.length) 319 | { 320 | inventory[lic] += a.queryInventory(_goodTypes[lic]); 321 | } 322 | } 323 | 324 | money /= list.length; 325 | for (lic in 0..._goodTypes.length) 326 | { 327 | inventory[lic] /= list.length; 328 | mr.arrStrListInventory[lic] += Quick.numStr(inventory[lic],1) + "\n"; 329 | } 330 | 331 | mr.strListAgentCount += Quick.numStr(count, 0) + "\n"; 332 | mr.strListAgentMoney += Quick.numStr(money, 0) + "\n"; 333 | } 334 | return mr; 335 | } 336 | 337 | /********PRIVATE*********/ 338 | 339 | private var _roundNum:Int = 0; 340 | 341 | private var _goodTypes:Array; //list of string ids for all the legal commodities 342 | private var _agents:Array; 343 | private var _book:TradeBook; 344 | private var _mapAgents:Map; 345 | private var _mapGoods:Map; 346 | 347 | private function fromData(data:MarketData) 348 | { 349 | //Create commodity index 350 | for (g in data.goods) 351 | { 352 | _goodTypes.push(g.id); 353 | _mapGoods.set(g.id, new Good(g.id, g.size)); 354 | 355 | history.register(g.id); 356 | history.prices.add(g.id, 1.0); //start the bidding at $1! 357 | history.asks.add(g.id, 1.0); //start history charts with 1 fake buy/sell bid 358 | history.bids.add(g.id, 1.0); 359 | history.trades.add(g.id, 1.0); 360 | 361 | _book.register(g.id); 362 | } 363 | 364 | _mapAgents = new Map(); 365 | 366 | for (aData in data.agentTypes) 367 | { 368 | _mapAgents.set(aData.className, aData); 369 | history.profit.register(aData.className); 370 | } 371 | 372 | //Make the agent list 373 | _agents = []; 374 | 375 | var agentIndex = 0; 376 | for (agent in data.agents) 377 | { 378 | agent.id = agentIndex; 379 | agent.init(this); 380 | _agents.push(agent); 381 | agentIndex++; 382 | } 383 | 384 | } 385 | 386 | private function resolveOffers(good:String = ""):Void 387 | { 388 | var bids:Array = _book.bids.get(good); 389 | var asks:Array = _book.asks.get(good); 390 | 391 | bids = Quick.shuffle(bids); 392 | asks = Quick.shuffle(asks); 393 | 394 | bids.sort(Quick.sortDecreasingPrice); //highest buying price first 395 | asks.sort(Quick.sortIncreasingPrice); //lowest selling price first 396 | 397 | var successfulTrades:Int = 0; //# of successful trades this round 398 | var moneyTraded:Float = 0; //amount of money traded this round 399 | var unitsTraded:Float = 0; //amount of goods traded this round 400 | var avgPrice:Float = 0; //avg clearing price this round 401 | var numAsks:Float = 0; 402 | var numBids:Float = 0; 403 | 404 | var failsafe:Int = 0; 405 | 406 | for (i in 0...bids.length) 407 | { 408 | numBids += bids[i].units; 409 | } 410 | 411 | for (i in 0...asks.length) 412 | { 413 | numAsks += asks[i].units; 414 | } 415 | 416 | //march through and try to clear orders 417 | while (bids.length > 0 && asks.length > 0) //while both books are non-empty 418 | { 419 | var buyer:Offer = bids[0]; 420 | var seller:Offer = asks[0]; 421 | 422 | var quantity_traded = Math.min(seller.units, buyer.units); 423 | var clearing_price = Quick.avgf(seller.unit_price, buyer.unit_price); 424 | 425 | if (quantity_traded > 0) 426 | { 427 | //transfer the goods for the agreed price 428 | seller.units -= quantity_traded; 429 | buyer.units -= quantity_traded; 430 | 431 | transferGood(good, quantity_traded, seller.agent_id, buyer.agent_id); 432 | transferMoney(quantity_traded * clearing_price, seller.agent_id, buyer.agent_id); 433 | 434 | //update agent price beliefs based on successful transaction 435 | var buyer_a:BasicAgent = _agents[buyer.agent_id]; 436 | var seller_a:BasicAgent = _agents[seller.agent_id]; 437 | buyer_a.updatePriceModel(this, "buy", good, true, clearing_price); 438 | seller_a.updatePriceModel(this, "sell", good, true, clearing_price); 439 | 440 | //log the stats 441 | moneyTraded += (quantity_traded * clearing_price); 442 | unitsTraded += quantity_traded; 443 | successfulTrades++; 444 | } 445 | 446 | if (seller.units == 0) //seller is out of offered good 447 | { 448 | asks.splice(0, 1); //remove ask 449 | failsafe = 0; 450 | } 451 | if (buyer.units == 0) //buyer is out of offered good 452 | { 453 | bids.splice(0, 1); //remove bid 454 | failsafe = 0; 455 | } 456 | 457 | failsafe++; 458 | 459 | if (failsafe > 1000) 460 | { 461 | trace("BOINK!"); 462 | } 463 | } 464 | 465 | //reject all remaining offers, 466 | //update price belief models based on unsuccessful transaction 467 | while (bids.length > 0) 468 | { 469 | var buyer:Offer = bids[0]; 470 | var buyer_a:BasicAgent = _agents[buyer.agent_id]; 471 | buyer_a.updatePriceModel(this,"buy",good, false); 472 | bids.splice(0, 1); 473 | } 474 | while (asks.length > 0) 475 | { 476 | var seller:Offer = asks[0]; 477 | var seller_a:BasicAgent = _agents[seller.agent_id]; 478 | seller_a.updatePriceModel(this,"sell",good, false); 479 | asks.splice(0, 1); 480 | } 481 | 482 | //update history 483 | 484 | history.asks.add(good, numAsks); 485 | history.bids.add(good, numBids); 486 | history.trades.add(good, unitsTraded); 487 | 488 | if (unitsTraded > 0) 489 | { 490 | avgPrice = moneyTraded / cast(unitsTraded, Float); 491 | history.prices.add(good, avgPrice); 492 | } 493 | else 494 | { 495 | //special case: none were traded this round, use last round's average price 496 | history.prices.add(good, history.prices.average(good, 1)); 497 | avgPrice = history.prices.average(good,1); 498 | } 499 | 500 | _agents.sort(Quick.sortAgentAlpha); 501 | 502 | var curr_class:String = ""; 503 | var last_class:String = ""; 504 | var list:Array = null; 505 | var avg_profit:Float = 0; 506 | 507 | for (i in 0..._agents.length) 508 | { 509 | var a:BasicAgent = _agents[i]; //get current agent 510 | curr_class = a.className; //check its class 511 | if (curr_class != last_class) //new class? 512 | { 513 | if (list != null) //do we have a list built up? 514 | { 515 | //log last class' profit 516 | history.profit.add(last_class, Quick.listAvgf(list)); 517 | } 518 | list = []; //make a new list 519 | last_class = curr_class; 520 | } 521 | list.push(a.profit); //push profit onto list 522 | } 523 | 524 | //add the last class too 525 | history.profit.add(last_class, Quick.listAvgf(list)); 526 | 527 | //sort by id so everything works again 528 | _agents.sort(Quick.sortAgentId); 529 | 530 | } 531 | 532 | private function transferGood(good:String, units:Float, seller_id:Int, buyer_id:Int):Void 533 | { 534 | var seller:BasicAgent = _agents[seller_id]; 535 | var buyer:BasicAgent = _agents[buyer_id]; 536 | seller.changeInventory(good, -units); 537 | buyer.changeInventory(good, units); 538 | } 539 | 540 | private function transferMoney(amount:Float, seller_id:Int, buyer_id:Int):Void 541 | { 542 | var seller:BasicAgent = _agents[seller_id]; 543 | var buyer:BasicAgent = _agents[buyer_id]; 544 | seller.money += amount; 545 | buyer.money -= amount; 546 | } 547 | 548 | } 549 | -------------------------------------------------------------------------------- /bazaarbot/MarketData.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot; 2 | import bazaarbot.agent.BasicAgent; 3 | import bazaarbot.agent.BasicAgent.AgentData; 4 | import bazaarbot.agent.InventoryData; 5 | import bazaarbot.agent.Logic; 6 | 7 | /** 8 | * ... 9 | * @author larsiusprime 10 | */ 11 | class MarketData 12 | { 13 | public var goods:Array; 14 | public var agentTypes:Array; 15 | public var agents:Array; 16 | 17 | public function new(goods:Array, agentTypes:Array, agents:Array) 18 | { 19 | this.goods = goods; 20 | this.agentTypes = agentTypes; 21 | this.agents = agents; 22 | } 23 | 24 | /** 25 | * Parse a market settings file to construct everything 26 | * @param data the JSON file definition for your Market 27 | * @param getAgent a function to create agents 28 | */ 29 | 30 | public static function fromJSON(json:Dynamic, getAgent:AgentData->BasicAgent):MarketData 31 | { 32 | var goods:Array = []; 33 | 34 | //Create goods index 35 | var jsonGoods:Array = json.goods; 36 | for (g in jsonGoods) 37 | { 38 | goods.push(new Good(g.id, g.size)); 39 | } 40 | 41 | var agentTypes:Array = []; 42 | 43 | //Create agent classes 44 | var jsonAgents:Array = json.agents; 45 | 46 | for (a in jsonAgents) 47 | { 48 | var agentData:AgentData = 49 | { 50 | className:a.id, 51 | money:a.money, 52 | inventory:InventoryData.fromJson(a.inventory), 53 | logicName:a.id, 54 | logic:null 55 | } 56 | 57 | for (g in goods) 58 | { 59 | agentData.inventory.size.set(g.id, g.size); 60 | } 61 | 62 | agentTypes.push(agentData); 63 | } 64 | 65 | //Make the agent list 66 | var agents:Array = []; 67 | 68 | //Get start conditions 69 | var startConditions:Dynamic = json.start_conditions; 70 | var starts = Reflect.fields(startConditions.agents); 71 | 72 | var agentIndex:Int = 0; 73 | //Make given number of each agent type 74 | 75 | for (classStr in starts) 76 | { 77 | var val:Int = Reflect.field(startConditions.agents, classStr); 78 | var agentData = null; 79 | for (i in 0...agentTypes.length) { 80 | if (agentTypes[i].className == classStr) 81 | { 82 | agentData = agentTypes[i]; 83 | break; 84 | } 85 | } 86 | 87 | for (i in 0...val) 88 | { 89 | var a:BasicAgent = getAgent(agentData); 90 | a.id = agentIndex; 91 | agentIndex++; 92 | agents.push(a); 93 | } 94 | } 95 | 96 | return new MarketData(goods, agentTypes, agents); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /bazaarbot/Offer.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot; 2 | 3 | /** 4 | * ... 5 | * @author 6 | */ 7 | class Offer 8 | { 9 | public var good:String; //the thing offered 10 | public var units:Float; //how many units 11 | public var unit_price:Float; //price per unit 12 | public var agent_id:Int; //who offered this 13 | 14 | public function new(agent_id_:Int=-1,commodity_:String="",units_:Float=1.0,unit_price_:Float=1.0) 15 | { 16 | agent_id = agent_id_; 17 | good = commodity_; 18 | units = units_; 19 | unit_price = unit_price_; 20 | } 21 | 22 | public function toString():String 23 | { 24 | return "("+agent_id + "): " + good + "x " + units + " @ " + unit_price; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bazaarbot/agent/BasicAgent.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot.agent; 2 | import bazaarbot.agent.Inventory; 3 | import bazaarbot.utils.EconNoun; 4 | import bazaarbot.utils.Quick; 5 | import openfl.Assets; 6 | import openfl.geom.Point; 7 | import bazaarbot.agent.InventoryData; 8 | import bazaarbot.agent.Logic; 9 | 10 | /** 11 | * The most fundamental agent class, and has as little implementation as possible. 12 | * In most cases you should start by extending Agent instead of this. 13 | * @author larsiusprime 14 | */ 15 | 16 | @:allow(Market) 17 | class BasicAgent 18 | { 19 | public var id:Int; //unique integer identifier 20 | public var className:String; //string identifier, "famer", "woodcutter", etc. 21 | public var money:Float; 22 | public var moneyLastRound(default, null):Float; 23 | public var profit(get, null):Float; 24 | public var inventorySpace(get, null):Float; 25 | public var inventoryFull(get, null):Bool; 26 | public var destroyed(default, null):Bool; 27 | 28 | public function new(id:Int, data:AgentData) 29 | { 30 | this.id = id; 31 | className = data.className; 32 | money = data.money; 33 | _inventory = new Inventory(); 34 | _inventory.fromData(data.inventory); 35 | _logic = data.logic; 36 | 37 | if (data.lookBack == null) 38 | { 39 | _lookback = 15; 40 | } 41 | else 42 | { 43 | _lookback = data.lookBack; 44 | } 45 | 46 | _priceBeliefs = new Map(); 47 | _observedTradingRange = new Map>(); 48 | } 49 | 50 | public function destroy():Void 51 | { 52 | destroyed = true; 53 | _inventory.destroy(); 54 | for (key in _priceBeliefs.keys()) 55 | { 56 | _priceBeliefs.remove(key); 57 | } 58 | for (key in _observedTradingRange.keys()) 59 | { 60 | var list:Array = _observedTradingRange.get(key); 61 | while (list.length > 0) { 62 | list.splice(0, 1); 63 | }list = null; 64 | _observedTradingRange.remove(key); 65 | } 66 | _priceBeliefs = null; 67 | _observedTradingRange = null; 68 | _logic = null; 69 | } 70 | 71 | public function init(market:Market):Void 72 | { 73 | var listGoods = market.getGoods_unsafe(); 74 | for (str in listGoods) 75 | { 76 | var trades:Array = new Array(); 77 | 78 | var price:Float = market.getAverageHistoricalPrice(str, _lookback); 79 | trades.push(price * 0.5); 80 | trades.push(price * 1.5); //push two fake trades to generate a range 81 | 82 | //set initial price belief & observed trading range 83 | _observedTradingRange.set(str, trades); 84 | _priceBeliefs.set(str, new Point(price * 0.5, price * 1.5)); 85 | } 86 | } 87 | 88 | public function simulate(market:Market):Void 89 | { 90 | _logic.perform(this, market); 91 | } 92 | 93 | public function generateOffers(bazaar:Market, good:String):Void 94 | { 95 | //no implemenation -- provide your own in a subclass 96 | } 97 | 98 | public function updatePriceModel(bazaar:Market, act:String, good:String, success:Bool, unitPrice:Float = 0):Void 99 | { 100 | //no implementation -- provide your own in a subclass 101 | } 102 | 103 | public function createBid(bazaar:Market, good:String, limit:Float):Offer 104 | { 105 | //no implementation -- provide your own in a subclass 106 | return null; 107 | } 108 | 109 | public function createAsk(bazaar:Market, commodity_:String, limit_:Float):Offer 110 | { 111 | //no implementation -- provide your own in a subclass 112 | return null; 113 | } 114 | 115 | public function queryInventory(good:String):Float 116 | { 117 | return _inventory.query(good); 118 | } 119 | 120 | public function changeInventory(good:String, delta:Float):Void 121 | { 122 | _inventory.change(good, delta); 123 | } 124 | 125 | /********PRIVATE************/ 126 | 127 | private var _logic:Logic; 128 | private var _inventory:Inventory; 129 | private var _priceBeliefs:Map; 130 | private var _observedTradingRange:Map>; 131 | private var _profit:Float = 0; //profit from last round 132 | private var _lookback:Int = 15; 133 | 134 | private function get_inventorySpace():Float 135 | { 136 | return _inventory.getEmptySpace(); 137 | } 138 | 139 | public function get_inventoryFull():Bool 140 | { 141 | return _inventory.getEmptySpace() == 0; 142 | } 143 | 144 | private function get_profit():Float 145 | { 146 | return money - moneyLastRound; 147 | } 148 | 149 | private function determinePriceOf(commodity_:String):Float 150 | { 151 | var belief:Point = _priceBeliefs.get(commodity_); 152 | return Quick.randomRange(belief.x, belief.y); 153 | } 154 | 155 | private function determineSaleQuantity(bazaar:Market, commodity_:String):Float 156 | { 157 | var mean:Float = bazaar.getAverageHistoricalPrice(commodity_,_lookback); 158 | var trading_range:Point = observeTradingRange(commodity_); 159 | if (trading_range != null) 160 | { 161 | var favorability:Float = Quick.positionInRange(mean, trading_range.x, trading_range.y); 162 | //position_in_range: high means price is at a high point 163 | 164 | var amount_to_sell:Float = Math.round(favorability * _inventory.surplus(commodity_)); 165 | if (amount_to_sell < 1) 166 | { 167 | amount_to_sell = 1; 168 | } 169 | return amount_to_sell; 170 | } 171 | return 0; 172 | } 173 | 174 | private function determinePurchaseQuantity(bazaar:Market, commodity_:String):Float 175 | { 176 | var mean:Float = bazaar.getAverageHistoricalPrice(commodity_,_lookback); 177 | var trading_range:Point = observeTradingRange(commodity_); 178 | if (trading_range != null) 179 | { 180 | var favorability:Float = Quick.positionInRange(mean, trading_range.x, trading_range.y); 181 | favorability = 1 - favorability; 182 | //do 1 - favorability to see how close we are to the low end 183 | 184 | var amount_to_buy:Float = Math.round(favorability * _inventory.shortage(commodity_)); 185 | if (amount_to_buy < 1) 186 | { 187 | amount_to_buy = 1; 188 | } 189 | return amount_to_buy; 190 | } 191 | return 0; 192 | } 193 | 194 | private function getPriceBelief(good:String):Point 195 | { 196 | return _priceBeliefs.get(good); 197 | } 198 | 199 | private function observeTradingRange(good:String):Point 200 | { 201 | var a:Array = _observedTradingRange.get(good); 202 | var pt:Point = new Point(Quick.minArr(a), Quick.maxArr(a)); 203 | return pt; 204 | } 205 | } 206 | 207 | typedef AgentData = { 208 | className:String, 209 | money:Float, 210 | inventory:InventoryData, 211 | logicName:String, 212 | logic:Logic, 213 | ?lookBack:Int 214 | } 215 | -------------------------------------------------------------------------------- /bazaarbot/agent/Inventory.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot.agent; 2 | 3 | /** 4 | * ... 5 | * @author 6 | */ 7 | class Inventory 8 | { 9 | public var maxSize:Float = 0; 10 | 11 | public function new() 12 | { 13 | _sizes = new Map(); 14 | _stuff = new Map(); 15 | _ideal = new Map(); 16 | maxSize = 0; 17 | } 18 | 19 | public function fromData(data:InventoryData) 20 | { 21 | var sizes = []; 22 | var amounts = []; 23 | for (key in data.start.keys()) 24 | { 25 | sizes.push(key); 26 | amounts.push(data.start.get(key)); 27 | } 28 | setStuff(sizes, amounts); 29 | sizes = []; 30 | amounts = []; 31 | for (key in data.size.keys()) 32 | { 33 | sizes.push(key); 34 | amounts.push(data.size.get(key)); 35 | } 36 | setSizes(sizes, amounts); 37 | sizes = []; 38 | amounts = []; 39 | for (key in data.ideal.keys()) 40 | { 41 | sizes.push(key); 42 | amounts.push(data.ideal.get(key)); 43 | setIdeal(sizes, amounts); 44 | } 45 | maxSize = data.maxSize; 46 | } 47 | 48 | public function copy():Inventory 49 | { 50 | var i:Inventory = new Inventory(); 51 | var stufff:Array = []; 52 | var stuffi:Array = []; 53 | var idealf:Array = []; 54 | var ideali:Array = []; 55 | var sizesf:Array = []; 56 | var sizesi:Array = []; 57 | for (key in _stuff.keys()) 58 | { 59 | stufff.push(_stuff.get(key)); 60 | stuffi.push(key); 61 | } 62 | for (key in _ideal.keys()) 63 | { 64 | idealf.push(_ideal.get(key)); 65 | ideali.push(key); 66 | } 67 | for (key in _sizes.keys()) 68 | { 69 | sizesf.push(_sizes.get(key)); 70 | sizesi.push(key); 71 | } 72 | i.setStuff(stuffi, stufff); 73 | i.setIdeal(ideali, idealf); 74 | i.setSizes(sizesi, sizesf); 75 | i.maxSize = maxSize; 76 | return i; 77 | } 78 | 79 | public function destroy():Void 80 | { 81 | for (key in _stuff.keys()) 82 | { 83 | _stuff.remove(key); 84 | } 85 | for (key in _ideal.keys()) 86 | { 87 | _ideal.remove(key); 88 | } 89 | for (key in _sizes.keys()) 90 | { 91 | _sizes.remove(key); 92 | } 93 | _stuff = null; 94 | _ideal = null; 95 | _sizes = null; 96 | } 97 | 98 | /** 99 | * Set amounts of various commodities 100 | * @param stuff_ 101 | * @param amounts_ 102 | */ 103 | 104 | public function setStuff(stuff:Array, amounts:Array):Void 105 | { 106 | for (i in 0...stuff.length) 107 | { 108 | _stuff.set(stuff[i], amounts[i]); 109 | } 110 | } 111 | 112 | /** 113 | * Set how much of each commodity to stockpile 114 | * @param stuff_ 115 | * @param amounts_ 116 | */ 117 | 118 | public function setIdeal(ideal:Array, amounts:Array):Void 119 | { 120 | for (i in 0...ideal.length) 121 | { 122 | _ideal.set(ideal[i], amounts[i]); 123 | } 124 | } 125 | 126 | public function setSizes(sizes:Array, amounts:Array):Void 127 | { 128 | for (i in 0...sizes.length) 129 | { 130 | _sizes.set(sizes[i], amounts[i]); 131 | } 132 | } 133 | 134 | /** 135 | * Returns how much of this 136 | * @param commodity_ string id of commodity 137 | * @return 138 | */ 139 | 140 | public function query(good:String):Float 141 | { 142 | if (_stuff.exists(good)) 143 | { 144 | return _stuff.get(good); 145 | } 146 | return 0; 147 | } 148 | 149 | public function ideal(good:String):Float 150 | { 151 | if (_ideal.exists(good)) 152 | { 153 | return _ideal.get(good); 154 | } 155 | return 0; 156 | } 157 | 158 | public function getEmptySpace():Float 159 | { 160 | return maxSize - getUsedSpace(); 161 | } 162 | 163 | public function getUsedSpace():Float 164 | { 165 | var space_used:Float = 0; 166 | for (key in _stuff.keys()) 167 | { 168 | space_used += _stuff.get(key) * _sizes.get(key); 169 | } 170 | return space_used; 171 | } 172 | 173 | public function getCapacityFor(good:String):Float 174 | { 175 | if (_sizes.exists(good)) 176 | { 177 | return _sizes.get(good); 178 | } 179 | return -1; 180 | } 181 | 182 | /** 183 | * Change the amount of the given commodity by delta 184 | * @param commodity_ string id of commodity 185 | * @param delta_ amount added 186 | */ 187 | 188 | public function change(good:String, delta:Float):Void 189 | { 190 | var result:Float; 191 | 192 | if (_stuff.exists(good)) 193 | { 194 | var amount:Float = _stuff.get(good); 195 | result = amount + delta; 196 | } 197 | else 198 | { 199 | result = delta; 200 | } 201 | 202 | if (result < 0) 203 | { 204 | result = 0; 205 | } 206 | 207 | _stuff.set(good, result); 208 | } 209 | 210 | /** 211 | * Returns # of units above the desired inventory level, or 0 if @ or below 212 | * @param commodity_ string id of commodity 213 | * @return 214 | */ 215 | 216 | public function surplus(good:String):Float 217 | { 218 | var amt:Float = query(good); 219 | var ideal:Float = _ideal.get(good); 220 | if (amt > ideal) 221 | { 222 | return (amt - ideal); 223 | } 224 | return 0; 225 | } 226 | 227 | /** 228 | * Returns # of units below the desired inventory level, or 0 if @ or above 229 | * @param commodity_ 230 | * @return 231 | */ 232 | 233 | public function shortage(good:String):Float 234 | { 235 | if (!_stuff.exists(good)) 236 | { 237 | return 0; 238 | } 239 | var amt:Float = query(good); 240 | var ideal:Float = _ideal.get(good); 241 | if (amt < ideal) 242 | { 243 | return (ideal - amt); 244 | } 245 | return 0; 246 | } 247 | 248 | //private static var _index:Map; 249 | 250 | private var _stuff:Map; // key:commodity_id, val:amount 251 | private var _ideal:Map; // ideal counts for each thing 252 | private var _sizes:Map; // how much space each thing takes up 253 | } 254 | -------------------------------------------------------------------------------- /bazaarbot/agent/InventoryData.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot.agent; 2 | 3 | /** 4 | * ... 5 | * @author larsiusprime 6 | */ 7 | class InventoryData 8 | { 9 | public var maxSize:Float; 10 | public var ideal:Map; 11 | public var start:Map; 12 | public var size:Map; 13 | 14 | public function new(maxSize:Float, ideal:Map, start:Map, size:Map) 15 | { 16 | this.maxSize = maxSize; 17 | this.ideal = ideal; 18 | this.start = start; 19 | this.size = size; 20 | } 21 | 22 | public static function fromJson(data:Dynamic):InventoryData 23 | { 24 | var maxSize:Int = data.max_size; 25 | var ideal = new Map(); 26 | var start = new Map(); 27 | var size = new Map(); 28 | 29 | var startArray = Reflect.fields(data.start); 30 | if (startArray != null) 31 | { 32 | for (s in startArray) 33 | { 34 | start.set(s, cast Reflect.field(data.start, s)); 35 | size.set(s, 1); //initialize size of every item to 1 by default 36 | } 37 | } 38 | var idealArray = Reflect.fields(data.ideal); 39 | if (idealArray != null) 40 | { 41 | for (i in idealArray) 42 | { 43 | ideal.set(i, cast Reflect.field(data.ideal, i)); 44 | } 45 | } 46 | 47 | return new InventoryData(maxSize, ideal, start, size); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /bazaarbot/agent/Logic.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot.agent; 2 | import haxe.io.Path; 3 | import haxe.Json; 4 | import hscript.Interp; 5 | import hscript.Parser; 6 | import openfl.Assets; 7 | 8 | /** 9 | * ... 10 | * @author 11 | */ 12 | class Logic 13 | { 14 | private var init:Bool = false; 15 | 16 | public function new(?data:Dynamic) 17 | { 18 | //no implemenation -- provide your own in a subclass 19 | } 20 | 21 | /** 22 | * Perform this logic on the given agent 23 | * @param agent 24 | */ 25 | 26 | public function perform(agent:BasicAgent, market:Market):Void 27 | { 28 | //no implemenation -- provide your own in a subclass 29 | } 30 | 31 | private function _produce(agent:BasicAgent, commodity:String, amount:Float, chance:Float = 1.0):Void 32 | { 33 | if (chance >= 1.0 || Math.random() < chance) 34 | { 35 | agent.changeInventory(commodity, amount); 36 | } 37 | } 38 | 39 | private function _consume(agent:BasicAgent, commodity:String, amount:Float, chance:Float = 1.0):Void 40 | { 41 | if (chance >= 1.0 || Math.random() < chance) 42 | { 43 | if (commodity == "money") 44 | { 45 | agent.money -= amount; 46 | } 47 | else 48 | { 49 | agent.changeInventory(commodity, -amount); 50 | } 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /bazaarbot/agent/LogicHScript.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot.agent; 2 | import bazaarbot.Market; 3 | import bazaarbot.Agent; 4 | import hscript.Interp; 5 | import hscript.Parser; 6 | import openfl.Assets; 7 | 8 | /** 9 | * ... 10 | * @author larsiusprime 11 | */ 12 | class LogicHScript extends Logic 13 | { 14 | var script:String = ""; 15 | var source:String; 16 | 17 | public function new(?data:Dynamic) 18 | { 19 | super(data); 20 | if (data == null) return; 21 | if (Std.is(data, String)) 22 | { 23 | script = Assets.getText("assets/scripts/"+data); 24 | } 25 | } 26 | 27 | override public function perform(agent:Agent, bazaar:Market):Void 28 | { 29 | _perform_script(script, agent, bazaar); 30 | } 31 | 32 | private function _perform_script(script:String, agent:Agent, bazaar:Market):Void 33 | { 34 | var parser = new Parser(); 35 | var ast = parser.parseString(script); 36 | var interp = new Interp(); 37 | 38 | var vars:Map = 39 | [ 40 | "agent" => agent, 41 | "query_inventory" => agent.queryInventory, 42 | "produce" => _produce, 43 | "consume" => _consume, 44 | "inventory_is_full" => agent.get_inventoryFull, 45 | "make_room_for" => 46 | function(a:Agent, c:String = "food", amt:Float = 1.0):Void 47 | { 48 | var to_drop:String = bazaar.getCheapestCommodity(10, [c]); 49 | if (to_drop != "") {_consume(a, to_drop, amt);} 50 | } 51 | ]; 52 | 53 | interp.variables = vars; 54 | interp.execute(ast); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /bazaarbot/agent/LogicScript.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot.agent; 2 | import bazaarbot.Market; 3 | import bazaarbot.agent.BasicAgent; 4 | import hscript.Interp; 5 | import hscript.Parser; 6 | import openfl.Assets; 7 | 8 | /** 9 | * ... 10 | * @author larsiusprime 11 | */ 12 | class LogicScript extends Logic 13 | { 14 | var script:String = ""; 15 | var source:String; 16 | 17 | public function new(?data:Dynamic) 18 | { 19 | super(data); 20 | if (data == null) return; 21 | if (Std.is(data, String)) 22 | { 23 | script = Assets.getText("assets/scripts/"+data); 24 | } 25 | } 26 | 27 | override public function perform(agent:BasicAgent, market:Market):Void 28 | { 29 | _perform_script(script, agent, market); 30 | } 31 | 32 | private function _perform_script(script:String, agent:BasicAgent, market:Market):Void 33 | { 34 | var parser = new Parser(); 35 | var ast = parser.parseString(script); 36 | var interp = new Interp(); 37 | 38 | var vars:Map = 39 | [ 40 | "agent" => agent, 41 | "query_inventory" => agent.queryInventory, 42 | "produce" => _produce, 43 | "consume" => _consume, 44 | "inventory_is_full" => agent.get_inventoryFull, 45 | "make_room_for" => 46 | function(a:BasicAgent, c:String = "food", amt:Float = 1.0):Void 47 | { 48 | var to_drop:String = market.getCheapestGood(10, [c]); 49 | if (to_drop != "") {_consume(a, to_drop, amt);} 50 | } 51 | ]; 52 | 53 | interp.variables = vars; 54 | interp.execute(ast); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /bazaarbot/utils/ArrayUtil.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot.utils; 2 | 3 | /** 4 | * A set of functions for array manipulation. (Copied from HaxeFlixel) 5 | */ 6 | class ArrayUtil 7 | { 8 | /** 9 | * Sets the length of an array. 10 | * 11 | * @param array The array. 12 | * @param newLength The length you want the array to have. 13 | */ 14 | @:generic 15 | public static function setLength(array:Array, newLength:Int):Void 16 | { 17 | if (newLength < 0) return; 18 | var oldLength:Int = array.length; 19 | var diff:Int = newLength - oldLength; 20 | if (diff < 0) 21 | { 22 | #if flash 23 | untyped array.length = newLength; 24 | #else 25 | diff = -diff; 26 | for (i in 0...diff) 27 | { 28 | array.pop(); 29 | } 30 | #end 31 | } 32 | } 33 | 34 | /** 35 | * Safely removes an element from an array by swapping it with the last element and calling pop() 36 | * (won't do anything if the element is not in the array). This is a lot faster than regular splice(), 37 | * but it can only be used on arrays where order doesn't matter. 38 | * 39 | * @param array The array to remove the element from 40 | * @param element The element to remove from the array 41 | * @return The array 42 | */ 43 | @:generic 44 | public static inline function fastSplice(array:Array, element:T):Array 45 | { 46 | var index = array.indexOf(element); 47 | if (index != -1) 48 | { 49 | return swapAndPop(array, index); 50 | } 51 | return array; 52 | } 53 | 54 | /** 55 | * Removes an element from an array by swapping it with the last element and calling pop(). 56 | * This is a lot faster than regular splice(), but it can only be used on arrays where order doesn't matter. 57 | * 58 | * IMPORTANT: always count down from length to zero if removing elements from whithin a loop 59 | * 60 | * var i = array.length; 61 | * while (i-- > 0) 62 | * { 63 | * if (array[i].shouldRemove) 64 | * { 65 | * ArrayUtil.swapAndPop(array, i); 66 | * } 67 | * } 68 | * 69 | * @param array The array to remove the element from 70 | * @param index The index of the element to be removed from the array 71 | * @return The array 72 | */ 73 | @:generic 74 | public static inline function swapAndPop(array:Array, index:Int):Array 75 | { 76 | array[index] = array[array.length - 1]; // swap element to remove and last element 77 | array.pop(); 78 | return array; 79 | } 80 | 81 | /** 82 | * Clears an array structure, but leaves the object data untouched 83 | * Useful for cleaning up temporary references to data you want to preserve 84 | * WARNING: Can lead to memory leaks. Use destroyArray() instead for data you truly want GONE. 85 | * 86 | * @param array The array to clear out 87 | * @param Recursive Whether to search for arrays inside of arr and clear them out, too (false by default) 88 | */ 89 | public static function clearArray(array:Array, recursive:Bool = false):Void 90 | { 91 | if (array != null) 92 | { 93 | if (!recursive) 94 | { 95 | while (array.length > 0) 96 | { 97 | array.pop(); 98 | } 99 | } 100 | else 101 | { 102 | while (array.length > 0) 103 | { 104 | var thing:T = array.pop(); 105 | if (Std.is(thing, Array)) 106 | { 107 | clearArray(array, recursive); 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | /** 115 | * Flattens 2D arrays into 1D arrays. 116 | * Example: [[1, 2], [3, 2], [1, 1]] -> [1, 2, 3, 2, 1, 1] 117 | */ 118 | @:generic 119 | public static function flatten2DArray(array:Array>):Array 120 | { 121 | var result = []; 122 | 123 | for (innerArray in array) 124 | { 125 | result = result.concat(innerArray); 126 | } 127 | 128 | return result; 129 | } 130 | 131 | /** 132 | * Compares the contents with == to see if the two arrays are the same. 133 | * Also takes null arrays and the length of the arrays into account. 134 | */ 135 | public static function equals(array1:Array, array2:Array):Bool 136 | { 137 | if (array1 == null && array2 == null) 138 | return true; 139 | if (array1 == null && array2 != null) 140 | return false; 141 | if (array1 != null && array2 == null) 142 | return false; 143 | if (array1.length != array2.length) 144 | return false; 145 | 146 | for (i in 0...array1.length) 147 | { 148 | if (array1[i] != array2[i]) 149 | { 150 | return false; 151 | } 152 | } 153 | return true; 154 | } 155 | 156 | /** 157 | * Returns the last element of an array or null if the array is null / empty. 158 | */ 159 | public static function last(array:Array):Null 160 | { 161 | if (array == null || array.length == 0) 162 | return null; 163 | return array[array.length - 1]; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /bazaarbot/utils/DestroyUtil.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot.utils; 2 | 3 | /** 4 | * (Copied from HaxeFlixel) 5 | */ 6 | 7 | class DestroyUtil 8 | { 9 | /** 10 | * Checks if an object is not null before calling destroy(), always returns null. 11 | * 12 | * @param object An IDestroyable object that will be destroyed if it's not null. 13 | * @return null 14 | */ 15 | public static function destroy(object:Null):T 16 | { 17 | if (object != null) 18 | { 19 | object.destroy(); 20 | } 21 | return null; 22 | } 23 | 24 | /** 25 | * Destroy every element of an array of IDestroyables 26 | * 27 | * @param array An Array of IDestroyable objects 28 | * @return null 29 | */ 30 | public static function destroyArray(array:Array):Array 31 | { 32 | if (array != null) 33 | { 34 | for (e in array) destroy(e); 35 | array.splice(0, array.length); 36 | } 37 | return null; 38 | } 39 | } 40 | 41 | interface IDestroyable 42 | { 43 | public function destroy():Void; 44 | } 45 | -------------------------------------------------------------------------------- /bazaarbot/utils/EconNoun.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot.utils; 2 | 3 | /** 4 | * @author larsiusprime 5 | */ 6 | 7 | enum EconNoun 8 | { 9 | Price; 10 | Ask; 11 | Bid; 12 | Trade; 13 | Profit; 14 | } 15 | -------------------------------------------------------------------------------- /bazaarbot/utils/History.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot.utils; 2 | 3 | /** 4 | * ... 5 | * @author larsiusprime 6 | */ 7 | class History 8 | { 9 | public var prices(default, null):HistoryLog; 10 | public var asks (default, null):HistoryLog; 11 | public var bids (default, null):HistoryLog; 12 | public var trades(default, null):HistoryLog; 13 | public var profit(default, null):HistoryLog; 14 | 15 | public function new() 16 | { 17 | prices = new HistoryLog(Price); 18 | asks = new HistoryLog(Ask); 19 | bids = new HistoryLog(Bid); 20 | trades = new HistoryLog(Trade); 21 | profit = new HistoryLog(Profit); 22 | } 23 | 24 | public function register(good:String) 25 | { 26 | prices.register(good); 27 | asks.register(good); 28 | bids.register(good); 29 | trades.register(good); 30 | profit.register(good); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /bazaarbot/utils/HistoryLog.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot.utils; 2 | import bazaarbot.utils.EconNoun; 3 | 4 | /** 5 | * ... 6 | * @author larsiusprime 7 | */ 8 | class HistoryLog 9 | { 10 | var type:EconNoun; 11 | var log:Map>; 12 | 13 | public function new(type:EconNoun) 14 | { 15 | this.type = type; 16 | log = new Map>(); 17 | } 18 | 19 | /** 20 | * Add a new entry to this log 21 | * @param name 22 | * @param amount 23 | */ 24 | public function add(name:String, amount:Float) 25 | { 26 | if (log.exists(name)) 27 | { 28 | var list = log.get(name); 29 | list.push(amount); 30 | } 31 | } 32 | 33 | /** 34 | * Register a new category list in this log 35 | * @param name 36 | */ 37 | public function register(name:String) 38 | { 39 | if (!log.exists(name)) 40 | { 41 | log.set(name, new Array()); 42 | } 43 | } 44 | 45 | /** 46 | * Returns the average amount of the given category, looking backwards over a specified range 47 | * @param name the category of thing 48 | * @param range how far to look back 49 | * @return 50 | */ 51 | public function average(name:String, range:Int):Float 52 | { 53 | if (log.exists(name)) 54 | { 55 | var list = log.get(name); 56 | var amt:Float = 0.0; 57 | var length = list.length; 58 | if (length < range) 59 | { 60 | range = length; 61 | } 62 | for (i in 0...range) 63 | { 64 | amt += list[length - 1 - i]; 65 | } 66 | return amt / range; 67 | } 68 | return 0; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /bazaarbot/utils/MarketReport.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot.utils; 2 | 3 | /** 4 | * ... 5 | * @author 6 | */ 7 | class MarketReport 8 | { 9 | public var strListGood:String = ""; 10 | public var strListGoodPrices:String = ""; 11 | public var strListGoodTrades:String = ""; 12 | public var strListGoodAsks:String = ""; 13 | public var strListGoodBids:String = ""; 14 | 15 | public var strListAgent:String = ""; 16 | public var strListAgentCount:String = ""; 17 | public var strListAgentMoney:String = ""; 18 | public var strListAgentProfit:String = ""; 19 | 20 | public var arrStrListInventory:Array; 21 | 22 | public function new() 23 | { 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /bazaarbot/utils/Quick.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot.utils; 2 | import bazaarbot.agent.BasicAgent; 3 | 4 | /** 5 | * A quick & dirty utility class 6 | * @author larsiusprime 7 | */ 8 | class Quick 9 | { 10 | 11 | public static inline function avgf(a:Float, b:Float):Float 12 | { 13 | return (a + b) / 2; 14 | } 15 | 16 | public static function listAvgf(list:Array):Float 17 | { 18 | var avg:Float = 0; 19 | for (j in 0...list.length) 20 | { 21 | avg += list[j]; 22 | } 23 | avg /= list.length; 24 | return avg; 25 | } 26 | 27 | public static inline function minArr(a:Array):Float 28 | { 29 | var min:Float = Math.POSITIVE_INFINITY; 30 | for (f in a) { 31 | if (f < min) { min = f;} 32 | } 33 | return min; 34 | } 35 | 36 | public static inline function maxArr(a:Array):Float 37 | { 38 | var max:Float = Math.NEGATIVE_INFINITY; 39 | for (f in a) { 40 | if (f > max) { max = f; } 41 | } 42 | return max; 43 | } 44 | 45 | /** 46 | * Turns a number into a string with the specified number of decimal points 47 | * @param num 48 | * @param decimals 49 | * @return 50 | */ 51 | public static function numStr(num:Float, decimals:Int):String 52 | { 53 | var tens:Float = 1; 54 | for (i in 0...decimals) 55 | { 56 | tens *= 10; 57 | } 58 | num = Math.floor(num * tens) / tens; 59 | var str:String = Std.string(num); 60 | var split = str.split("."); 61 | if (split.length == 2) 62 | { 63 | if (split[1].length < decimals) 64 | { 65 | var diff:Int = decimals - split[1].length; 66 | for (i in 0...diff) 67 | { 68 | str += "0"; 69 | } 70 | } 71 | if (decimals > 0) 72 | { 73 | str = split[0] + "." + split[1].substr(0, decimals); 74 | } 75 | else 76 | { 77 | str = split[0]; 78 | } 79 | } 80 | else 81 | { 82 | if (decimals > 0) 83 | { 84 | str += "."; 85 | for (i in 0...decimals) 86 | { 87 | str += "0"; 88 | } 89 | } 90 | } 91 | return str; 92 | } 93 | 94 | public static inline function positionInRange(value:Float, min:Float, max:Float, clamp:Bool = true):Float 95 | { 96 | value -= min; 97 | max -= min; 98 | min = 0; 99 | value = (value / (max - min)); 100 | if (clamp) { 101 | if (value < 0) { value = 0; } 102 | if (value > 1) { value = 1; } 103 | } 104 | return value; 105 | } 106 | 107 | public static inline function randomInteger(min:Int, max:Int):Int 108 | { 109 | return Std.int(Math.random() * cast(1 + max - min, Float)) + min; 110 | } 111 | 112 | public static inline function randomRange(a:Float, b:Float):Float 113 | { 114 | var r:Float = Math.random(); 115 | var min:Float = a < b ? a : b; 116 | var max:Float = a < b ? b : a; 117 | var range:Float = max - min; 118 | return r * range + min; 119 | } 120 | 121 | public static function shuffle(list:Array):Array 122 | { 123 | /* 124 | To shuffle an array a of n elements (indices 0..n-1): 125 | for i from n − 1 downto 1 do 126 | j ← random integer with 0 ≤ j ≤ i 127 | exchange a[j] and a[i] 128 | */ 129 | for (i in 0...list.length - 1) 130 | { 131 | var ii:Int = (list.length - 1) - i; 132 | if (ii > 1) 133 | { 134 | var j:Int = randomInteger(0, ii); 135 | var temp = list[j]; 136 | list[j] = list[ii]; 137 | list[ii] = temp; 138 | } 139 | } 140 | return list; 141 | } 142 | 143 | public static function sortAgentAlpha(a:BasicAgent, b:BasicAgent):Int 144 | { 145 | if (a.className < b.className) return -1; 146 | if (a.className > b.className) return 1; 147 | return 0; 148 | } 149 | 150 | public static function sortAgentId(a:BasicAgent, b:BasicAgent):Int 151 | { 152 | if (a.id < b.id) return -1; 153 | if (a.id > b.id) return 1; 154 | return 0; 155 | } 156 | 157 | public static function sortDecreasingPrice(a:Offer, b:Offer):Int 158 | { 159 | //Decreasing means: highest first 160 | if (a.unit_price < b.unit_price) return 1; 161 | if (a.unit_price > b.unit_price) return -1; 162 | return 0; 163 | } 164 | 165 | public static function sortIncreasingPrice(a:Offer, b:Offer):Int 166 | { 167 | //Increasing means: lowest first 168 | if (a.unit_price > b.unit_price) return 1; 169 | if (a.unit_price < b.unit_price) return -1; 170 | return 0; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /bazaarbot/utils/Signal.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot.utils; 2 | import bazaarbot.utils.DestroyUtil; 3 | import bazaarbot.utils.DestroyUtil.IDestroyable; 4 | 5 | /** 6 | * (Copied from HaxeFlixel) 7 | */ 8 | 9 | #if macro 10 | import haxe.macro.Expr; 11 | #else 12 | 13 | typedef Signal = TypedSignalVoid>; 14 | 15 | @:multiType 16 | abstract TypedSignal(ISignal) 17 | { 18 | public var dispatch(get, never):T; 19 | 20 | public function new(); 21 | 22 | public inline function add(listener:T):Void 23 | { 24 | this.add(listener); 25 | } 26 | 27 | public inline function addOnce(listener:T):Void 28 | { 29 | this.addOnce(listener); 30 | } 31 | 32 | public inline function remove(listener:T):Void 33 | { 34 | this.remove(listener); 35 | } 36 | 37 | public inline function has(listener:T):Bool 38 | { 39 | return this.has(listener); 40 | } 41 | 42 | public inline function removeAll():Void 43 | { 44 | this.removeAll(); 45 | } 46 | 47 | private inline function get_dispatch():T 48 | { 49 | return this.dispatch; 50 | } 51 | 52 | @:to 53 | private static inline function toSignal0(signal:ISignalVoid>):Signal0 54 | { 55 | return new Signal0(); 56 | } 57 | 58 | @:to 59 | private static inline function toSignal1(signal:ISignalVoid>):Signal1 60 | { 61 | return new Signal1(); 62 | } 63 | 64 | @:to 65 | private static inline function toSignal2(signal:ISignalT2->Void>):Signal2 66 | { 67 | return new Signal2(); 68 | } 69 | 70 | @:to 71 | private static inline function toSignal3(signal:ISignalT2->T3->Void>):Signal3 72 | { 73 | return new Signal3(); 74 | } 75 | 76 | @:to 77 | private static inline function toSignal4(signal:ISignalT2->T3->T4->Void>):Signal4 78 | { 79 | return new Signal4(); 80 | } 81 | } 82 | 83 | private class SignalHandler implements IDestroyable 84 | { 85 | public var listener:T; 86 | public var dispatchOnce(default, null):Bool = false; 87 | 88 | public function new(listener:T, dispatchOnce:Bool) 89 | { 90 | this.listener = listener; 91 | this.dispatchOnce = dispatchOnce; 92 | } 93 | 94 | public function destroy() 95 | { 96 | listener = null; 97 | } 98 | } 99 | 100 | private class BaseSignal implements ISignal 101 | { 102 | /** 103 | * Typed function reference used to dispatch this signal. 104 | */ 105 | public var dispatch:T; 106 | 107 | private var handlers:Array>; 108 | private var pendingRemove:Array>; 109 | private var processingListeners:Bool = false; 110 | 111 | 112 | public function new() 113 | { 114 | handlers = []; 115 | pendingRemove = []; 116 | } 117 | 118 | public function add(listener:T) 119 | { 120 | if (listener != null) 121 | registerListener(listener, false); 122 | } 123 | 124 | public function addOnce(listener:T):Void 125 | { 126 | if (listener != null) 127 | registerListener(listener, true); 128 | } 129 | 130 | public function remove(listener:T):Void 131 | { 132 | if (listener != null) 133 | { 134 | var handler = getHandler(listener); 135 | if (handler != null) 136 | { 137 | if (processingListeners) 138 | pendingRemove.push(handler); 139 | else 140 | { 141 | handlers.remove(handler); 142 | handler.destroy(); 143 | } 144 | } 145 | } 146 | 147 | } 148 | 149 | public function has(listener:T):Bool 150 | { 151 | if (listener == null) 152 | return false; 153 | return getHandler(listener) != null; 154 | } 155 | 156 | public inline function removeAll():Void 157 | { 158 | DestroyUtil.destroyArray(handlers); 159 | } 160 | 161 | public function destroy():Void 162 | { 163 | removeAll(); 164 | handlers = null; 165 | pendingRemove = null; 166 | } 167 | 168 | private function registerListener(listener:T, dispatchOnce:Bool):SignalHandler 169 | { 170 | var handler = getHandler(listener); 171 | 172 | if (handler == null) 173 | { 174 | handler = new SignalHandler(listener, dispatchOnce); 175 | handlers.push(handler); 176 | return handler; 177 | } 178 | else 179 | { 180 | // If the listener was previously added, definitely don't add it again. 181 | // But throw an exception if their once values differ. 182 | if (handler.dispatchOnce != dispatchOnce) 183 | throw "You cannot addOnce() then add() the same listener without removing the relationship first."; 184 | else 185 | return handler; 186 | } 187 | } 188 | 189 | private function getHandler(listener:T):SignalHandler 190 | { 191 | for (handler in handlers) 192 | { 193 | if ( 194 | #if neko // simply comparing the functions doesn't do the trick on neko 195 | Reflect.compareMethods(handler.listener, listener) 196 | #else 197 | handler.listener == listener 198 | #end ) 199 | { 200 | return handler; // Listener was already registered. 201 | } 202 | } 203 | return null; // Listener not yet registered. 204 | } 205 | } 206 | 207 | private class Signal0 extends BaseSignalVoid> 208 | { 209 | public function new() 210 | { 211 | super(); 212 | this.dispatch = dispatch0; 213 | } 214 | 215 | public function dispatch0():Void 216 | { 217 | Macro.buildDispatch(); 218 | } 219 | } 220 | 221 | private class Signal1 extends BaseSignalVoid> 222 | { 223 | public function new() 224 | { 225 | super(); 226 | this.dispatch = dispatch1; 227 | } 228 | 229 | public function dispatch1(value1:T1):Void 230 | { 231 | Macro.buildDispatch(value1); 232 | } 233 | } 234 | 235 | private class Signal2 extends BaseSignalT2->Void> 236 | { 237 | public function new() 238 | { 239 | super(); 240 | this.dispatch = dispatch2; 241 | } 242 | 243 | public function dispatch2(value1:T1, value2:T2):Void 244 | { 245 | Macro.buildDispatch(value1, value2); 246 | } 247 | } 248 | 249 | private class Signal3 extends BaseSignalT2->T3->Void> 250 | { 251 | public function new() 252 | { 253 | super(); 254 | this.dispatch = dispatch3; 255 | } 256 | 257 | public function dispatch3(value1:T1, value2:T2, value3:T3):Void 258 | { 259 | Macro.buildDispatch(value1, value2, value3); 260 | } 261 | } 262 | 263 | private class Signal4 extends BaseSignalT2->T3->T4->Void> 264 | { 265 | public function new() 266 | { 267 | super(); 268 | this.dispatch = dispatch4; 269 | } 270 | 271 | public function dispatch4(value1:T1, value2:T2, value3:T3, value4:T4):Void 272 | { 273 | Macro.buildDispatch(value1, value2, value3, value4); 274 | } 275 | } 276 | 277 | interface ISignal extends IDestroyable 278 | { 279 | public var dispatch:T; 280 | public function add(listener:T):Void; 281 | public function addOnce(listener:T):Void; 282 | public function remove(listener:T):Void; 283 | public function removeAll():Void; 284 | public function has(listener:T):Bool; 285 | } 286 | 287 | #end 288 | 289 | private class Macro 290 | { 291 | macro public static function buildDispatch(exprs:Array):Expr 292 | { 293 | return macro 294 | { 295 | processingListeners = true; 296 | for (handler in handlers) 297 | { 298 | handler.listener($a{exprs}); 299 | 300 | if (handler.dispatchOnce) 301 | remove(handler.listener); 302 | } 303 | 304 | processingListeners = false; 305 | 306 | for (handler in pendingRemove) 307 | { 308 | remove(handler.listener); 309 | } 310 | if (pendingRemove.length > 0) 311 | pendingRemove = []; 312 | } 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /bazaarbot/utils/TradeBook.hx: -------------------------------------------------------------------------------- 1 | package bazaarbot.utils; 2 | 3 | /** 4 | * ... 5 | * @author larsiusprime 6 | */ 7 | class TradeBook 8 | { 9 | public var bids:Map>; 10 | public var asks:Map>; 11 | 12 | public function new() 13 | { 14 | bids = new Map>(); 15 | asks = new Map>(); 16 | } 17 | 18 | public function register(name:String) 19 | { 20 | asks.set(name, new Array()); 21 | bids.set(name, new Array()); 22 | } 23 | 24 | public function bid(offer:Offer):Bool 25 | { 26 | if (!bids.exists(offer.good)) 27 | return false; 28 | 29 | bids.get(offer.good).push(offer); 30 | return true; 31 | } 32 | 33 | public function ask(offer:Offer):Bool 34 | { 35 | if (!bids.exists(offer.good)) 36 | return false; 37 | 38 | asks.get(offer.good).push(offer); 39 | return true; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/Assets/nme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larsiusprime/bazaarBot/f93df8b1fb1c78794a393a2f6b07af8c2fe6e44b/examples/doran_and_parberry/Assets/nme.png -------------------------------------------------------------------------------- /examples/doran_and_parberry/Assets/nme.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/Assets/scripts/best_job.hs: -------------------------------------------------------------------------------- 1 | var best_job = ""; 2 | 3 | if(commodity == "food") {best_job = "farmer";} 4 | else if(commodity == "wood") {best_job = "woodcutter";} 5 | else if(commodity == "ore") {best_job = "miner";} 6 | else if(commodity == "metal") {best_job = "refiner";} 7 | else if(commodity == "tools") {best_job = "blacksmith";} 8 | 9 | best_job; 10 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/Assets/scripts/blacksmith.hs: -------------------------------------------------------------------------------- 1 | var food = query_inventory("food"); 2 | var metal = query_inventory("metal"); 3 | 4 | var has_food = food >= 1; 5 | var has_metal = metal >= 1; 6 | 7 | if(has_food && has_metal){ 8 | //convert all metal into tools 9 | produce(agent,"tools",metal); 10 | consume(agent,"metal",metal); 11 | }else{ 12 | //fined $2 for being idle 13 | consume(agent,"money",2); 14 | if(!has_food && inventory_is_full()){ 15 | make_room_for(agent,"food",2); 16 | } 17 | } -------------------------------------------------------------------------------- /examples/doran_and_parberry/Assets/scripts/farmer.hs: -------------------------------------------------------------------------------- 1 | var wood = query_inventory("wood"); 2 | var tools = query_inventory("tools"); 3 | 4 | var has_wood = wood >= 1; 5 | var has_tools = tools >= 1; 6 | 7 | if(has_wood){ 8 | if(has_tools){ 9 | //produce 4 food, consume 1 wood, break tools with 10% chance 10 | produce(agent,"food",4,1); 11 | consume(agent,"wood",1,1); 12 | consume(agent,"tools",1,0.1); 13 | }else{ 14 | //produce 2 food, consume 1 wood 15 | produce(agent,"food",2,1); 16 | consume(agent,"wood",1,1); 17 | } 18 | }else{ 19 | //fined $2 for being idle 20 | consume(agent,"money",2); 21 | } 22 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/Assets/scripts/miner.hs: -------------------------------------------------------------------------------- 1 | var food = query_inventory("food"); 2 | var tools = query_inventory("tools"); 3 | 4 | var has_food = food >= 1; 5 | var has_tools = tools >= 1; 6 | 7 | if(has_food){ 8 | if(has_tools){ 9 | //produce 4 ore, consume 1 food, break tools with 10% chance 10 | produce(agent,"ore",4); 11 | consume(agent,"food",1); 12 | consume(agent,"tools",1,0.1); 13 | }else{ 14 | //produce 2 ore, consume 1 food 15 | produce(agent,"ore",2); 16 | consume(agent,"food",1); 17 | } 18 | }else{ 19 | //fined $2 for being idle 20 | consume(agent,"money",2); 21 | if(!has_food && inventory_is_full()){ 22 | make_room_for(agent,"food",2); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/Assets/scripts/refiner.hs: -------------------------------------------------------------------------------- 1 | var food = query_inventory("food"); 2 | var tools = query_inventory("tools"); 3 | var ore = query_inventory("ore"); 4 | 5 | var has_food = food >= 1; 6 | var has_tools = tools >= 1; 7 | var has_ore = ore >= 1; 8 | 9 | if(has_food && has_ore){ 10 | if(has_tools){ 11 | //convert all ore into metal, consume 1 food, break tools with 10% chance 12 | produce(agent,"metal",ore); 13 | consume(agent,"ore",ore); 14 | consume(agent,"food",1); 15 | consume(agent,"tools",1,0.1); 16 | }else{ 17 | //convert up to 2 ore into metal, consume 1 food 18 | var max = query_inventory("ore"); 19 | if(max > 2){ max = 2;} 20 | produce(agent,"metal", max); 21 | consume(agent,"ore", max); 22 | consume(agent,"food",1); 23 | } 24 | }else{ 25 | //fined $2 for being idle 26 | consume(agent,"money",2); 27 | if(!has_food && inventory_is_full()){ 28 | make_room_for(agent,"food",2); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/Assets/scripts/woodcutter.hs: -------------------------------------------------------------------------------- 1 | var food = query_inventory("food"); 2 | var tools = query_inventory("tools"); 3 | 4 | var has_food = food >= 1; 5 | var has_tools = tools >= 1; 6 | 7 | if(has_food){ 8 | if(has_tools){ 9 | //produce 2 wood, consume 1 food, break tools with 10% chance 10 | produce(agent,"wood",2); 11 | consume(agent,"food",1); 12 | consume(agent,"tools",1,0.1); 13 | }else{ 14 | //produce 1 wood, consume 1 food 15 | produce(agent,"wood",1); 16 | consume(agent,"food",1); 17 | } 18 | }else{ 19 | //fined $2 for being idle 20 | consume(agent,"money",2); 21 | if(!has_food && inventory_is_full()){ 22 | make_room_for(agent,"food",2); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/Assets/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "start_conditions":{ 3 | "agents":{ 4 | "farmer":10, 5 | "miner":10, 6 | "woodcutter":10, 7 | "refiner":10, 8 | "blacksmith":10 9 | } 10 | }, 11 | "goods":[ 12 | {"id":"food" ,"size":"0.5"}, 13 | {"id":"wood" ,"size":"1.0"}, 14 | {"id":"ore" ,"size":"1.0"}, 15 | {"id":"metal","size":"1.0"}, 16 | {"id":"tools","size":"1.0"} 17 | ], 18 | "agents":[ 19 | { 20 | "id":"farmer", 21 | "money":100, 22 | "inventory":{ 23 | "start":{"food":0,"tools":1,"wood":0}, 24 | "ideal":{"food":0,"tools":1,"wood":3}, 25 | "max_size":20 26 | }, 27 | "logic":"farmer" 28 | }, 29 | { 30 | "id":"miner", 31 | "money":100, 32 | "inventory":{ 33 | "start":{"food":1,"tools":1,"ore":0}, 34 | "ideal":{"food":3,"tools":1,"ore":0}, 35 | "max_size":20 36 | }, 37 | "logic":"miner" 38 | }, 39 | { 40 | "id":"refiner", 41 | "money":100, 42 | "inventory":{ 43 | "start":{"food":1,"tools":1,"metal":0,"ore":0}, 44 | "ideal":{"food":3,"tools":1,"metal":0,"ore":5}, 45 | "max_size":20 46 | }, 47 | "logic":"refiner" 48 | }, 49 | { 50 | "id":"woodcutter", 51 | "money":100, 52 | "inventory":{ 53 | "start":{"food":1,"tools":1,"wood":0}, 54 | "ideal":{"food":3,"tools":1,"wood":0}, 55 | "max_size":20 56 | }, 57 | "logic":"woodcutter" 58 | }, 59 | { 60 | "id":"blacksmith", 61 | "money":100, 62 | "inventory":{ 63 | "start":{"food":1,"tools":0,"metal":0,"ore":0}, 64 | "ideal":{"food":3,"tools":1,"metal":5,"ore":0}, 65 | "max_size":20 66 | }, 67 | "logic":"blacksmith" 68 | } 69 | ] 70 | } -------------------------------------------------------------------------------- /examples/doran_and_parberry/example.hxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | "$(CompilerPath)/haxelib" run lime build "$(OutputFile)" $(TargetBuild) -$(BuildConfig) -Dfdb 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/source/DoranAndParberryEconomy.hx: -------------------------------------------------------------------------------- 1 | package; 2 | import bazaarbot.Agent; 3 | import bazaarbot.agent.BasicAgent; 4 | import bazaarbot.agent.Logic; 5 | import bazaarbot.agent.LogicScript; 6 | import bazaarbot.Economy; 7 | import bazaarbot.Market; 8 | import bazaarbot.MarketData; 9 | import haxe.Json; 10 | import jobs.LogicBlacksmith; 11 | import jobs.LogicFarmer; 12 | import jobs.LogicMiner; 13 | import jobs.LogicRefiner; 14 | import jobs.LogicWoodcutter; 15 | import openfl.Assets; 16 | 17 | /** 18 | * ... 19 | * @author larsiusprime 20 | */ 21 | class DoranAndParberryEconomy extends Economy 22 | { 23 | 24 | public function new() 25 | { 26 | super(); 27 | var market = new Market("default"); 28 | market.init(MarketData.fromJSON(Json.parse(Assets.getText("assets/settings.json")), getAgent)); 29 | addMarket(market); 30 | } 31 | 32 | override function onBankruptcy(m:Market, a:BasicAgent):Void 33 | { 34 | replaceAgent(m, a); 35 | } 36 | 37 | private function replaceAgent(market:Market, agent:BasicAgent):Void 38 | { 39 | var bestClass:String = market.getMostProfitableAgentClass(); 40 | 41 | //Special case to deal with very high demand-to-supply ratios 42 | //This will make them favor entering an underserved market over 43 | //Just picking the most profitable class 44 | var bestGood:String = market.getHottestGood(); 45 | 46 | if (bestGood != "") 47 | { 48 | var bestGoodClass:String = getAgentClassThatMakesMost(bestGood); 49 | if (bestGoodClass != "") 50 | { 51 | bestClass = bestGoodClass; 52 | } 53 | } 54 | 55 | var newAgent = getAgent(market.getAgentClass(bestClass)); 56 | market.replaceAgent(agent, newAgent); 57 | } 58 | 59 | 60 | /** 61 | * Get the average amount of a given good that a given agent class has 62 | * @param className 63 | * @param good 64 | * @return 65 | */ 66 | /* 67 | public function getAgentClassAverageInventory(className:String, good:String):Float 68 | { 69 | var list = _agents.filter(function(a:BasicAgent):Bool { return a.className == className; } ); 70 | var amount:Float = 0; 71 | for (agent in list) 72 | { 73 | amount += agent.queryInventory(good); 74 | } 75 | amount /= list.length; 76 | return amount; 77 | } 78 | */ 79 | 80 | /** 81 | * Find the agent class that produces the most of a given good 82 | * @param good 83 | * @return 84 | */ 85 | public function getAgentClassThatMakesMost(good:String):String 86 | { 87 | return if (good == "food" ) {"farmer"; } 88 | else if (good == "wood" ) {"woodcutter"; } 89 | else if (good == "ore" ) {"miner"; } 90 | else if (good == "metal") {"refiner"; } 91 | else if (good == "tools") { "blacksmith"; } 92 | else ""; 93 | } 94 | 95 | /** 96 | * Find the agent class that has the most of a given good 97 | * @param good 98 | * @return 99 | */ 100 | /* 101 | public function getAgentClassWithMost(good:String):String 102 | { 103 | var amount:Float = 0; 104 | var bestAmount:Float = 0; 105 | var bestClass:String = ""; 106 | for (key in _mapAgents.keys()) 107 | { 108 | amount = getAverageInventory(key, good); 109 | if (amount > bestAmount) 110 | { 111 | bestAmount = amount; 112 | bestClass = key; 113 | } 114 | } 115 | return bestClass; 116 | } 117 | */ 118 | 119 | private function getAgentScript(data:AgentData):BasicAgent 120 | { 121 | data.logic = new LogicScript(data.logicName+".hs"); 122 | return new Agent(0, data); 123 | } 124 | 125 | private function getAgent(data:AgentData):BasicAgent 126 | { 127 | data.logic = getLogic(data.logicName); 128 | return new Agent(0, data); 129 | } 130 | 131 | private function getLogic(str:String):Logic 132 | { 133 | switch(str) 134 | { 135 | case "blacksmith": return new LogicBlacksmith(null); 136 | case "farmer": return new LogicFarmer(null); 137 | case "miner": return new LogicMiner(null); 138 | case "refiner": return new LogicRefiner(null); 139 | case "woodcutter": return new LogicWoodcutter(null); 140 | } 141 | return null; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/source/Main.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import bazaarbot.Economy; 4 | import bazaarbot.Market; 5 | import bazaarbot.utils.Quick; 6 | import flash.Lib; 7 | import flash.text.TextField; 8 | import flash.text.TextFieldAutoSize; 9 | import flash.text.TextFormat; 10 | import flash.display.SimpleButton; 11 | import flash.display.Sprite; 12 | import openfl.Assets; 13 | import flash.events.MouseEvent; 14 | import flash.text.TextFormatAlign; 15 | 16 | 17 | class Main extends Sprite 18 | { 19 | private var economy:Economy; 20 | private var market:Market; 21 | private var display:MarketDisplay; 22 | private var txt_benchmark:TextField; 23 | 24 | public function new () 25 | { 26 | super (); 27 | 28 | economy = new DoranAndParberryEconomy(); 29 | 30 | market = economy.getMarket("default"); 31 | 32 | makeButtons(); 33 | } 34 | 35 | private function makeButtons():Void 36 | { 37 | makeButton(10, 10, "Advance", onAdvance); 38 | 39 | display = new MarketDisplay(799, 600 - 51); 40 | display.x = 0; 41 | display.y = 50; 42 | addChild(display); 43 | 44 | makeButton(120, 10, "Benchmark", onBenchmark); 45 | txt_benchmark = new TextField(); 46 | txt_benchmark.x = 220; 47 | txt_benchmark.y = 10; 48 | txt_benchmark.width = 800 - 220; 49 | addChild(txt_benchmark); 50 | } 51 | 52 | private function onBenchmark(m:MouseEvent):Void 53 | { 54 | var time = Lib.getTimer(); 55 | var benchmark:Int = 30; 56 | market.simulate(benchmark); 57 | display.update(market.get_marketReport(1)); 58 | time = Lib.getTimer() - time; 59 | var avg:Float = (cast(time, Float) / cast(benchmark, Float)) / 1000; 60 | var tstr:String = Quick.numStr(time / 1000, 2); 61 | var platform:String="NONE"; 62 | #if flash 63 | platform = "flash"; 64 | #elseif cpp 65 | platform = "cpp"; 66 | #elseif neko 67 | platform = "neko"; 68 | #elseif js 69 | platform = "js"; 70 | #end 71 | 72 | txt_benchmark.text = ("Platform=" + platform + " Rounds=" + benchmark + ", Commodities=" + market.numTypesOfGood() + ", Agents=" + market.numAgents() + ", TIME total=" + tstr + " avg=" + Quick.numStr(avg,2)); 73 | } 74 | 75 | private function onAdvance(m:MouseEvent):Void 76 | { 77 | market.simulate(1); 78 | display.update(market.get_marketReport(1)); 79 | } 80 | 81 | private function makeButton(X:Float, Y:Float, str:String, func:Dynamic, W:Float = 100, H:Float = 30):SimpleButton 82 | { 83 | var up:Sprite = new Sprite(); 84 | var over:Sprite = new Sprite(); 85 | var down:Sprite = new Sprite(); 86 | var hit:Sprite = new Sprite(); 87 | up.graphics.beginFill(0xaaaaaa); 88 | up.graphics.drawRoundRect(0, 0, W, H, 5, 5); 89 | up.graphics.endFill(); 90 | 91 | over.graphics.beginFill(0xdddddd); 92 | over.graphics.drawRoundRect(0, 0, W, H, 5, 5); 93 | over.graphics.endFill(); 94 | 95 | down.graphics.beginFill(0x777777); 96 | down.graphics.drawRoundRect(0, 0, W, H, 5, 5); 97 | down.graphics.endFill(); 98 | 99 | hit.graphics.beginFill(0x000000); 100 | hit.graphics.drawRoundRect(0, 0, W, H, 5, 5); 101 | hit.graphics.endFill(); 102 | 103 | var text1:TextField = new TextField(); 104 | var text2:TextField = new TextField(); 105 | var text3:TextField = new TextField(); 106 | 107 | up.addChild(text1); 108 | over.addChild(text2); 109 | down.addChild(text3); 110 | 111 | text1.autoSize = TextFieldAutoSize.LEFT; 112 | text1.text = text2.text = text3.text = str; 113 | text1.x = text2.x = text3.x = (up.width - text1.textWidth)/2; 114 | text1.y = text2.y = text3.y = (up.height - text1.height) / 2; 115 | 116 | var s:SimpleButton = new SimpleButton(up, over, down, hit); 117 | s.addEventListener(MouseEvent.CLICK, func, false, 0, true); 118 | 119 | s.x = X; 120 | s.y = Y; 121 | 122 | addChild(s); 123 | return s; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/source/MarketDisplay.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import bazaarbot.Market; 4 | import bazaarbot.utils.MarketReport; 5 | import flash.display.Sprite; 6 | import flash.text.TextField; 7 | import flash.text.TextFormatAlign; 8 | 9 | /** 10 | * ... 11 | * @author 12 | */ 13 | class MarketDisplay extends Sprite 14 | { 15 | private var txtListGood:TextField; 16 | private var txtListGoodPrices:TextField; 17 | private var txtListGoodTrades:TextField; 18 | private var txtListGoodAsks:TextField; 19 | private var txtListGoodBids:TextField; 20 | 21 | private var txtListAgent:TextField; 22 | private var txtListAgentCount:TextField; 23 | private var txtListAgentProfit:TextField; 24 | private var txtListAgentMoney:TextField; 25 | 26 | private var arrTxtListInventory:Array; 27 | 28 | public function new(W:Float,H:Float) 29 | { 30 | super(); 31 | graphics.lineStyle(1, 0); 32 | graphics.beginFill(0xEEEEEE); 33 | graphics.drawRect(0, 0, W, H); 34 | graphics.endFill(); 35 | 36 | setup(); 37 | } 38 | 39 | public function update(report:MarketReport):Void 40 | { 41 | txtListGood.text = report.strListGood; 42 | txtListGoodPrices.text = report.strListGoodPrices; 43 | txtListGoodTrades.text = report.strListGoodTrades; 44 | txtListGoodAsks.text = report.strListGoodAsks; 45 | txtListGoodBids.text = report.strListGoodBids; 46 | 47 | txtListAgent.text = report.strListAgent; 48 | txtListAgentCount.text = report.strListAgentCount; 49 | txtListAgentProfit.text = report.strListAgentProfit; 50 | txtListAgentMoney.text = report.strListAgentMoney; 51 | 52 | if (arrTxtListInventory == null) 53 | { 54 | setupTxtInventory(report); 55 | } 56 | for (i in 0...report.arrStrListInventory.length) 57 | { 58 | arrTxtListInventory[i].text = report.arrStrListInventory[i]; 59 | } 60 | } 61 | 62 | private function setupTxtInventory(report:MarketReport):Void 63 | { 64 | arrTxtListInventory = new Array(); 65 | var last_txt:TextField = txtListAgentMoney; 66 | for (str in report.arrStrListInventory) 67 | { 68 | var txt:TextField = new TextField(); 69 | txt.width = width * 0.05; 70 | txt.height = height * 0.25; 71 | txt.x = last_txt.x + last_txt.width; 72 | txt.y = 0; 73 | addChild(txt); 74 | arrTxtListInventory.push(txt); 75 | last_txt = txt; 76 | } 77 | } 78 | 79 | private function setup():Void 80 | { 81 | txtListGood = new TextField(); 82 | txtListGood.width = width * 0.10; 83 | txtListGood.height = height * 0.25; 84 | txtListGood.x = 0; 85 | txtListGood.y = 0; 86 | 87 | txtListGoodPrices = new TextField(); 88 | txtListGoodPrices.width = width * 0.075; 89 | txtListGoodPrices.height = height * 0.25; 90 | txtListGoodPrices.x = txtListGood.x+txtListGood.width; 91 | txtListGoodPrices.y = 0; 92 | 93 | txtListGoodTrades = new TextField(); 94 | txtListGoodTrades.width = width * 0.075; 95 | txtListGoodTrades.height = height * 0.25; 96 | txtListGoodTrades.x = txtListGoodPrices.x+txtListGoodPrices.width; 97 | txtListGoodTrades.y = 0; 98 | 99 | txtListGoodAsks = new TextField(); 100 | txtListGoodAsks.width = width * 0.075; 101 | txtListGoodAsks.height = height * 0.25; 102 | txtListGoodAsks.x = txtListGoodTrades.x+txtListGoodTrades.width; 103 | txtListGoodAsks.y = 0; 104 | 105 | txtListGoodBids = new TextField(); 106 | txtListGoodBids.width = width * 0.075; 107 | txtListGoodBids.height = height * 0.25; 108 | txtListGoodBids.x = txtListGoodAsks.x+txtListGoodAsks.width; 109 | txtListGoodBids.y = 0; 110 | 111 | txtListAgent = new TextField(); 112 | txtListAgent.width = width * 0.10; 113 | txtListAgent.height = height * 0.25; 114 | txtListAgent.x = txtListGoodBids.x + txtListGoodBids.width + 10; 115 | txtListAgent.y = 0; 116 | 117 | txtListAgentCount = new TextField(); 118 | txtListAgentCount.width = width * 0.075; 119 | txtListAgentCount.height = height * 0.25; 120 | txtListAgentCount.x = txtListAgent.x + txtListAgent.width + 5; 121 | txtListAgentCount.y = 0; 122 | 123 | txtListAgentProfit = new TextField(); 124 | txtListAgentProfit.width = width * 0.075; 125 | txtListAgentProfit.height = height * 0.25; 126 | txtListAgentProfit.x = txtListAgentCount.x + txtListAgentCount.width + 5; 127 | txtListAgentProfit.y = 0; 128 | 129 | txtListAgentMoney = new TextField(); 130 | txtListAgentMoney.width = width * 0.075; 131 | txtListAgentMoney.height = height * 0.25; 132 | txtListAgentMoney.x = txtListAgentProfit.x + txtListAgentProfit.width + 5; 133 | txtListAgentMoney.y = 0; 134 | 135 | txtListGoodPrices.defaultTextFormat.align = TextFormatAlign.RIGHT; 136 | txtListAgentProfit.defaultTextFormat.align = TextFormatAlign.RIGHT; 137 | 138 | addChild(txtListGood); 139 | addChild(txtListGoodPrices); 140 | addChild(txtListGoodTrades); 141 | addChild(txtListGoodAsks); 142 | addChild(txtListGoodBids); 143 | 144 | addChild(txtListAgent); 145 | addChild(txtListAgentCount); 146 | addChild(txtListAgentProfit); 147 | addChild(txtListAgentMoney); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/source/jobs/LogicBlacksmith.hx: -------------------------------------------------------------------------------- 1 | package jobs; 2 | import bazaarbot.Market; 3 | import bazaarbot.agent.BasicAgent; 4 | import bazaarbot.agent.Logic; 5 | 6 | /** 7 | * ... 8 | * @author larsiusprime 9 | */ 10 | class LogicBlacksmith extends LogicGeneric 11 | { 12 | 13 | public function new(?data) 14 | { 15 | super(data); 16 | } 17 | 18 | override public function perform(agent:BasicAgent, market:Market) 19 | { 20 | var food = agent.queryInventory("food"); 21 | var metal = agent.queryInventory("metal"); 22 | 23 | var has_food = food >= 1; 24 | var has_metal = metal >= 1; 25 | 26 | if (has_food && has_metal) 27 | { 28 | //convert all metal into tools 29 | _produce(agent,"tools",metal); 30 | _consume(agent,"metal",metal); 31 | } 32 | else 33 | { 34 | //fined $2 for being idle 35 | _consume(agent,"money",2); 36 | if (!has_food && agent.inventoryFull) 37 | { 38 | makeRoomFor(market, agent,"food",2); 39 | } 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/source/jobs/LogicFarmer.hx: -------------------------------------------------------------------------------- 1 | package jobs; 2 | import bazaarbot.agent.BasicAgent; 3 | import bazaarbot.Market; 4 | /** 5 | * ... 6 | * @author larsiusprime 7 | */ 8 | class LogicFarmer extends LogicGeneric 9 | { 10 | 11 | public function new(?data:Dynamic) 12 | { 13 | super(data); 14 | } 15 | 16 | override public function perform(agent:BasicAgent, market:Market) 17 | { 18 | var wood = agent.queryInventory("wood"); 19 | var tools = agent.queryInventory("tools"); 20 | 21 | var has_wood = wood >= 1; 22 | var has_tools = tools >= 1; 23 | 24 | if (has_wood) 25 | { 26 | if (has_tools) 27 | { 28 | //produce 4 food, consume 1 wood, break tools with 10% chance 29 | _produce(agent,"food",4,1); 30 | _consume(agent,"wood",1,1); 31 | _consume(agent,"tools",1,0.1); 32 | } 33 | else{ 34 | //produce 2 food, consume 1 wood 35 | _produce(agent,"food",2,1); 36 | _consume(agent,"wood",1,1); 37 | } 38 | } 39 | else 40 | { 41 | //fined $2 for being idle 42 | _consume(agent,"money",2); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /examples/doran_and_parberry/source/jobs/LogicGeneric.hx: -------------------------------------------------------------------------------- 1 | package jobs; 2 | import bazaarbot.agent.BasicAgent; 3 | import bazaarbot.agent.Logic; 4 | import bazaarbot.Market; 5 | /** 6 | * ... 7 | * @author larsiusprime 8 | */ 9 | class LogicGeneric extends Logic 10 | { 11 | 12 | public function new(?data:Dynamic) 13 | { 14 | super(data); 15 | } 16 | 17 | private function makeRoomFor(market:Market, agent:BasicAgent, good:String = "food", amt:Float = 1.0):Void 18 | { 19 | var toDrop:String = market.getCheapestGood(10, [good]); 20 | if (toDrop != "") 21 | { 22 | _consume(agent, toDrop, amt); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/source/jobs/LogicMiner.hx: -------------------------------------------------------------------------------- 1 | package jobs; 2 | import bazaarbot.agent.BasicAgent; 3 | import bazaarbot.Market; 4 | /** 5 | * ... 6 | * @author larsiusprime 7 | */ 8 | class LogicMiner extends LogicGeneric 9 | { 10 | 11 | public function new(?data:Dynamic) 12 | { 13 | super(data); 14 | } 15 | 16 | override public function perform(agent:BasicAgent, market:Market) 17 | { 18 | var food = agent.queryInventory("food"); 19 | var tools = agent.queryInventory("tools"); 20 | 21 | var has_food = food >= 1; 22 | var has_tools = tools >= 1; 23 | 24 | if (has_food) 25 | { 26 | if (has_tools) 27 | { 28 | //produce 4 ore, consume 1 food, break tools with 10% chance 29 | _produce(agent,"ore",4); 30 | _consume(agent,"food",1); 31 | _consume(agent,"tools",1,0.1); 32 | } 33 | else 34 | { 35 | //produce 2 ore, consume 1 food 36 | _produce(agent,"ore",2); 37 | _consume(agent,"food",1); 38 | } 39 | } 40 | else 41 | { 42 | //fined $2 for being idle 43 | _consume(agent,"money",2); 44 | if (!has_food && agent.inventoryFull) 45 | { 46 | makeRoomFor(market, agent,"food",2); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/source/jobs/LogicRefiner.hx: -------------------------------------------------------------------------------- 1 | package jobs; 2 | import bazaarbot.agent.BasicAgent; 3 | import bazaarbot.Market; 4 | /** 5 | * ... 6 | * @author larsiusprime 7 | */ 8 | class LogicRefiner extends LogicGeneric 9 | { 10 | 11 | public function new(?data:Dynamic) 12 | { 13 | super(data); 14 | } 15 | 16 | override public function perform(agent:BasicAgent, market:Market) 17 | { 18 | var food = agent.queryInventory("food"); 19 | var tools = agent.queryInventory("tools"); 20 | var ore = agent.queryInventory("ore"); 21 | 22 | var has_food = food >= 1; 23 | var has_tools = tools >= 1; 24 | var has_ore = ore >= 1; 25 | 26 | if (has_food && has_ore) 27 | { 28 | if (has_tools) 29 | { 30 | //convert all ore into metal, consume 1 food, break tools with 10% chance 31 | _produce(agent,"metal",ore); 32 | _consume(agent,"ore",ore); 33 | _consume(agent,"food",1); 34 | _consume(agent,"tools",1,0.1); 35 | } 36 | else{ 37 | //convert up to 2 ore into metal, consume 1 food 38 | var max = agent.queryInventory("ore"); 39 | if(max > 2){ max = 2;} 40 | _produce(agent,"metal", max); 41 | _consume(agent,"ore", max); 42 | _consume(agent,"food",1); 43 | } 44 | } 45 | else 46 | { 47 | //fined $2 for being idle 48 | _consume(agent,"money",2); 49 | if (!has_food && agent.inventoryFull) 50 | { 51 | makeRoomFor(market, agent,"food",2); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/doran_and_parberry/source/jobs/LogicWoodcutter.hx: -------------------------------------------------------------------------------- 1 | package jobs; 2 | import bazaarbot.agent.BasicAgent; 3 | import bazaarbot.Market; 4 | /** 5 | * ... 6 | * @author larsiusprime 7 | */ 8 | class LogicWoodcutter extends LogicGeneric 9 | { 10 | 11 | public function new(?data:Dynamic) 12 | { 13 | super(data); 14 | } 15 | 16 | override public function perform(agent:BasicAgent, market:Market) 17 | { 18 | var food = agent.queryInventory("food"); 19 | var tools = agent.queryInventory("tools"); 20 | 21 | var has_food = food >= 1; 22 | var has_tools = tools >= 1; 23 | 24 | if (has_food) 25 | { 26 | if (has_tools) 27 | { 28 | //produce 2 wood, consume 1 food, break tools with 10% chance 29 | _produce(agent,"wood",2); 30 | _consume(agent,"food",1); 31 | _consume(agent,"tools",1,0.1); 32 | } 33 | else 34 | { 35 | //produce 1 wood, consume 1 food 36 | _produce(agent,"wood",1); 37 | _consume(agent,"food",1); 38 | } 39 | } 40 | else 41 | { 42 | //fined $2 for being idle 43 | _consume(agent,"money",2); 44 | if (!has_food && agent.inventoryFull) 45 | { 46 | makeRoomFor(market, agent, "food",2); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bazaarbot", 3 | "url" : "https://github.com/larsiusprime/bazaarBot", 4 | "license": "MIT", 5 | "tags": ["economics","simulation","openfl"], 6 | "description": "A free-market simulator based on Doran & Parberry's 'Emergent Economies for Role Playing Games'", 7 | "version": "0.0.1", 8 | "releasenote": "Pre-alpha release", 9 | "contributors": ["larsiusprime"], 10 | "dependencies": { 11 | "openfl": "", 12 | "hscript": "" 13 | } 14 | } 15 | --------------------------------------------------------------------------------