├── .gitignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── build └── jssim.min.js ├── examples ├── example-ant.html ├── example-balls.html ├── example-dragon-curve.html ├── example-flocking.html ├── example-game-of-life.html ├── example-l-system.html ├── example-near-beer-game.html ├── example-pso.html ├── example-school-yard.html ├── example-solar.html ├── example-virus.html ├── good.png ├── infected.png ├── normal.png └── virus.png ├── index.js ├── package.json ├── src └── jssim.js └── test ├── ant-spec.js ├── balls-spec.js ├── flocking-spec.js ├── game-of-life-spec.js ├── l-system-spec.js ├── messenger-spec.js ├── min-pq-spec.js ├── near-beer-game-spec.js ├── pso-spec.js ├── queue-spec.js ├── render-spec.js ├── scheduler-spec.js ├── school-yard-spec.js ├── solar-spec.js ├── stack-spec.js └── virus-spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | thumbs.db 4 | *.log 5 | node_modules/ 6 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | 5 | before_script: 6 | - npm install -g grunt-cli 7 | 8 | after_success: 9 | - npm run coveralls -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | // Load the plugin that provides the "uglify" task. 3 | grunt.loadNpmTasks('grunt-contrib-uglify'); 4 | grunt.loadNpmTasks('grunt-mocha-test'); 5 | grunt.loadNpmTasks('grunt-eslint'); 6 | 7 | grunt.initConfig({ 8 | pkg: grunt.file.readJSON('package.json'), 9 | uglify: { 10 | options: { 11 | preserveComments: 'some', 12 | }, 13 | build: { 14 | src: 'src/jssim.js', 15 | dest: 'build/jssim.min.js', 16 | }, 17 | }, 18 | mochaTest: { 19 | test: { 20 | options: { 21 | reporter: 'spec', 22 | }, 23 | src: ['tests/**/*.js'], 24 | }, 25 | }, 26 | }); 27 | 28 | // Default task(s). 29 | grunt.registerTask('default', ['uglify']); 30 | grunt.registerTask('test', ['mochaTest']); 31 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Xianshun Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # js-simulator 2 | 3 | Inspired by the MASON Multiagent Simulation library in Java, js-simulator is a general-purpose discrete-event multiagent simulator for agent-based modelling and simulation. It was written entirely in Javascript. 4 | 5 | [![Build Status](https://travis-ci.org/chen0040/js-simulator.svg?branch=master)](https://travis-ci.org/chen0040/js-simulator) [![Coverage Status](https://coveralls.io/repos/github/chen0040/js-simulator/badge.svg?branch=master)](https://coveralls.io/github/chen0040/js-simulator?branch=master) 6 | 7 | 8 | # Install 9 | 10 | Run the following npm command to install 11 | 12 | ```bash 13 | npm install js-simulator 14 | ``` 15 | 16 | # Demo 17 | 18 | The following HTML demo is available: 19 | 20 | * Flocking Boids [HTML DEMO](https://rawgit.com/chen0040/js-simulator/master/examples/example-flocking.html) 21 | * Conway's Game of Life [HTML DEMO](https://rawgit.com/chen0040/js-simulator/master/examples/example-game-of-life.html) 22 | * School Yard [HTML DEMO](https://rawgit.com/chen0040/js-simulator/master/examples/example-school-yard.html) 23 | * L-System (Fractal Plant) [HTML DEMO](https://rawgit.com/chen0040/js-simulator/master/examples/example-l-system.html) 24 | * L-System (Dragon Curve) [HTML DEMO](https://rawgit.com/chen0040/js-simulator/master/examples/example-dragon-curve.html) 25 | * Ant System [HTML DEMO](https://rawgit.com/chen0040/js-simulator/master/examples/example-ant.html) 26 | * Particle Swarm [HTML DEMO](https://rawgit.com/chen0040/js-simulator/master/examples/example-pso.html) 27 | * Virus [HTML DEMO](https://rawgit.com/chen0040/js-simulator/master/examples/example-virus.html) 28 | * (Spring) Balls [HTML DEMO](https://rawgit.com/chen0040/js-simulator/master/examples/example-balls.html) 29 | * Solar System [HTML DEMO](https://rawgit.com/chen0040/js-simulator/master/examples/example-solar.html) 30 | * Near Beer Game [HTML DEMO](https://rawgit.com/chen0040/js-simulator/master/examples/example-near-beer-game.html) 31 | 32 | More demo and HTML GUI supports will be added in the subsequent releases. 33 | 34 | 35 | # Usage 36 | 37 | ### Create and schedule discrete events or agents 38 | 39 | The discrete-event simulator is managed via the Scheduler class, which can be created as shown below: 40 | 41 | ```javascript 42 | jssim = require('js-simulator'); 43 | var scheduler = new jssim.Scheduler(); 44 | ``` 45 | 46 | The scheduler schedules and fires events based on their time and rank (i.e. the order of the event) spec. 47 | 48 | To schedule the an event to fire at a particular time: 49 | 50 | ```javascript 51 | var rank = 1; // the higher the rank, the higher the priority assigned and the higher-rank event will be fired first for all events occurring at the same time interval 52 | var evt = new jssim.SimEvent(rank); 53 | evt.id = 20; 54 | evt.update = function(deltaTime) { 55 | console.log('event [' + this.id + '] with rank ' + this.rank + ' is fired at time ' + this.time); 56 | 57 | // the code below allows the evt to send message to another agent (i.e., the agent referred by receiver variable) 58 | /* 59 | var receiver_id = receiver.guid() 60 | this.sendMsg(receiver_id, { 61 | content: "Hello" 62 | }); 63 | */ 64 | 65 | // the code below allows the evt to process messages sent from another agent 66 | /* 67 | var messages = this.readInbox(); 68 | for(var i = 0; i < messages.length; ++i){ 69 | var msg = messages[i]; 70 | var sender_id = msg.sender; 71 | var recipient_id = msg.recipient; // should equal to this.guid() 72 | var time = msg.time; 73 | var rank = msg.rank; // the messages[0] contains the highest ranked message and last messages contains lowest ranked 74 | var content = msg.content; // for example the "Hello" text from the sendMsg code above 75 | } 76 | */ 77 | }; 78 | 79 | var time_to_fire = 10; // fire this event at time = 10 80 | scheduler.schedule(evt, time_to_fire); 81 | ``` 82 | 83 | The main logic for an event is defined in its update(deltaTime) method, as shown in the code above. Events with higher ranks and earlier time_to_fire will always be executed first by the scheduler. 84 | 85 | An event can also be sheduled to fire at a later time from the current time (e.g., such an event can be fired within another event): 86 | 87 | ```javascript 88 | var delta_time_later = 10; // the event will be fired 10 time units from now, where now refers to the current scheduler time 89 | scheduler.scheduleOnceIn(evt, delta_time_later); 90 | ``` 91 | 92 | In terms of multi-agent system, an event can be thought of as an agent. Such an agent may need to execute repeatedly. In the js-simulator, this is achieved by firing an event repeatedly at a fixed interval: 93 | 94 | ```javascript 95 | var interval = 2; // time interval between consecutive firing of the event 96 | var start_time = 12; // time to fire the event for the first time 97 | scheduler.scheduleRepeatingAt(evt, start_time, interval); 98 | ``` 99 | 100 | If the start_time is at from the start of the simulation, then the above scheduling can also be replaced by: 101 | 102 | ```javascript 103 | scheduler.scheduleRepatingIn(evt, interval); 104 | ``` 105 | 106 | ### Execute the scheduler loop for the main discrete-event simulation 107 | 108 | After the events/agents are scheduled, they are not fired immediately but only fired when scheduler.update() method is called, each call to scheduler.update() to move the 109 | time forward. At each time forwarded, events with higher rank will be executed (by calling their update(delaTime) method) first. Also events with the same rank will be shuffled before execution. 110 | 111 | The scheduler can be executed in the following loop: 112 | 113 | ```javascript 114 | while(scheduler.hasEvents()) { 115 | var evts_fired = scheduler.update(); 116 | } 117 | ``` 118 | 119 | The above will run until no more events to fire in the scheduler, to stop the scheduler at a particular instead, use the following loop: 120 | 121 | ```javascript 122 | while(scheduler.current_time < 20) { // stop the scheduler when current scheduler time is 20 123 | scheduler.update(); 124 | } 125 | ``` 126 | 127 | The current scheduler time can be obtained by calling (this is useful if we want to know the current time inside the scheduler): 128 | 129 | ```javascript 130 | var current_scheduler_time = scheduler.current_time; 131 | ``` 132 | 133 | # Sample Codes 134 | 135 | ### Flocking behavior Demo 136 | 137 | The source code below shows how to create a flocking of 15 boids (12 preys and 3 predators) that demonstrate the flocking principles: 138 | 139 | Firstly we will declare a Boid class the inherits from the jsssim.SimEvent class, which defines the behavior of a single boid: 140 | 141 | ```javascript 142 | var jssim = require('js-simulator'); 143 | 144 | 145 | var Boid = function(id, initial_x, initial_y, space, isPredator) { 146 | var rank = 1; 147 | jssim.SimEvent.call(this, rank); 148 | this.id = id; 149 | this.space = space; 150 | this.space.updateAgent(this, initial_x, initial_y); 151 | this.sight = 75; 152 | this.speed = 12; 153 | this.separation_space = 30; 154 | this.velocity = new jssim.Vector2D(Math.random(), Math.random()); 155 | this.isPredator = isPredator; 156 | this.border = 100; 157 | }; 158 | 159 | Boid.prototype = Object.create(jssim.SimEvent); 160 | Boid.prototype.update = function(deltaTime) { 161 | var boids = this.space.findAllAgents(); 162 | var pos = this.space.getLocation(this.id); 163 | 164 | if(this.isPredator) { 165 | var prey = null; 166 | var min_distance = 10000000; 167 | for (var boidId in boids) 168 | { 169 | var boid = boids[boidId]; 170 | if(!boid.isPredator) { 171 | var boid_pos = this.space.getLocation(boid.id); 172 | var distance = pos.distance(boid_pos); 173 | if(min_distance > distance){ 174 | min_distance = distance; 175 | prey = boid; 176 | } 177 | } 178 | } 179 | 180 | if(prey != null) { 181 | var prey_position = this.space.getLocation(prey.id); 182 | this.velocity.x += prey_position.x - pos.x; 183 | this.velocity.y += prey_position.y - pos.y; 184 | } 185 | } else { 186 | for (var boidId in boids) 187 | { 188 | var boid = boids[boidId]; 189 | var boid_pos = this.space.getLocation(boid.id); 190 | var distance = pos.distance(boid_pos); 191 | if (boid != this && !boid.isPredator) 192 | { 193 | if (distance < this.separation_space) 194 | { 195 | // Separation 196 | this.velocity.x += pos.x - boid_pos.x; 197 | this.velocity.y += pos.y - boid_pos.y; 198 | } 199 | else if (distance < this.sight) 200 | { 201 | // Cohesion 202 | this.velocity.x += (boid_pos.x - pos.x) * 0.05; 203 | this.velocity.y += (boid_pos.y - pos.y) * 0.05; 204 | } 205 | if (distance < this.sight) 206 | { 207 | // Alignment 208 | this.velocity.x += boid.velocity.x * 0.5; 209 | this.velocity.y += boid.velocity.y * 0.5; 210 | } 211 | } 212 | if (boid.isPredator && distance < this.sight) 213 | { 214 | // Avoid predators. 215 | this.velocity.x += pos.x - boid_pos.x; 216 | this.velocity.y += pos.y - boid_pos.y; 217 | } 218 | } 219 | } 220 | 221 | 222 | // check speed 223 | var speed = this.velocity.length(); 224 | if(speed > this.speed) { 225 | this.velocity.resize(this.speed); 226 | } 227 | 228 | pos.x += this.velocity.x; 229 | pos.y += this.velocity.y; 230 | 231 | // check boundary 232 | var val = this.boundary - this.border; 233 | if (pos.x < this.border) pos.x = this.boundary - this.border; 234 | if (pos.y < this.border) pos.y = this.boundary - this.border; 235 | if (pos.x > val) pos.x = this.border; 236 | if (pos.y > val) pos.y = this.border; 237 | 238 | console.log("boid [ " + this.id + "] is at (" + pos.x + ", " + pos.y + ") at time " + this.time); 239 | }; 240 | ``` 241 | 242 | Once the boid is defined we can then create and schedule the flocking event simulator using the code below: 243 | 244 | ```javascript 245 | var scheduler = new jssim.Scheduler(); 246 | scheduler.reset(); 247 | 248 | var space = new jssim.Space2D(); 249 | for(var i = 0; i < 15; ++i) { 250 | var is_predator = i > 12; 251 | var boid = new Boid(i, 0, 0, space, is_predator); 252 | scheduler.scheduleRepeatingIn(boid, 1); 253 | } 254 | 255 | while(scheduler.current_time < 20) { 256 | scheduler.update(); 257 | } 258 | 259 | 260 | ``` 261 | 262 | ### Conway's Game of Life 263 | 264 | The sample code below shows how to create the game of life simulation: 265 | 266 | ```javascript 267 | var jssim = require('js-simulator'); 268 | 269 | var CellularAgent = function(world) { 270 | jssim.SimEvent.call(this); 271 | this.world = world; 272 | }; 273 | 274 | CellularAgent.prototype = Object.create(jssim.SimEvent.prototype); 275 | CellularAgent.prototype.update = function (deltaTime) { 276 | var width = this.world.width; 277 | var height = this.world.height; 278 | var past_grid = this.world.makeCopy(); 279 | for(var i=0; i < width; ++i) { 280 | for(var j = 0; j < height; ++j) { 281 | var count = 0; 282 | for(var dx = -1; dx < 2; ++dx) { 283 | var x = i + dx; 284 | if (x >= width) { 285 | x = 0; 286 | } 287 | if (x < 0) { 288 | x = width - 1; 289 | } 290 | for(var dy = -1; dy < 2; ++dy) { 291 | var y = j + dy; 292 | if(y >= height) { 293 | y = 0; 294 | } 295 | if(y < 0) { 296 | y = height - 1; 297 | } 298 | count += past_grid.getCell(x, y); 299 | } 300 | } 301 | if (count <= 2 || count >= 5) { 302 | this.world.setCell(i, j, 0); // dead 303 | } 304 | if (count == 3) { 305 | this.world.setCell(i, j, 1); // live 306 | } 307 | } 308 | } 309 | }; 310 | 311 | var scheduler = new jssim.Scheduler(); 312 | var grid = new jssim.Grid(640, 640); 313 | 314 | scheduler.reset(); 315 | grid.reset(); 316 | 317 | grid.setCell(1, 0, 1); 318 | grid.setCell(2, 0, 1); 319 | grid.setCell(0, 1, 1); 320 | grid.setCell(1, 1, 1); 321 | grid.setCell(1, 2, 1); 322 | grid.setCell(2, 2, 1); 323 | grid.setCell(2, 3, 1); 324 | 325 | scheduler.scheduleRepeatingIn(new CellularAgent(grid), 1); 326 | 327 | while(scheduler.current_time < 20) { // this assumes that we want to terminate at time 20 328 | scheduler.update(); 329 | } 330 | ``` 331 | 332 | ### School Yard Demo 333 | 334 | The sample code below shows the school yard demo: 335 | 336 | ```javascript 337 | var Student = function(id, yard, network) { 338 | jssim.SimEvent.call(this); 339 | this.id = id; 340 | this.yard = yard; 341 | this.network = network; 342 | this.MAX_FORCES = 3.0; 343 | this.forceToSchoolMultiplier = 0.01; 344 | this.randomMultiplier = 0.1; 345 | }; 346 | Student.prototype = Object.create(jssim.SimEvent.prototype); 347 | Student.prototype.update = function(deltaTime) { 348 | var students = this.yard.findAllAgents(); 349 | 350 | 351 | var me = this.yard.getLocation(this.id); 352 | 353 | var sumForces = new jssim.Vector2D(0, 0); 354 | var forceVector = new jssim.Vector2D(0, 0); 355 | var edges = this.network.adj(this.id); 356 | var len = edges.length; 357 | for (var buddy = 0; buddy < len; ++buddy) 358 | { 359 | var e = edges[buddy]; 360 | var buddiness = e.info; 361 | 362 | 363 | var him = this.yard.getLocation(e.other(this.id)); 364 | 365 | if (buddiness >= 0) 366 | { 367 | forceVector.set((him.x - me.x) * buddiness, (him.y - me.y) * buddiness); 368 | if (forceVector.length() > this.MAX_FORCES) 369 | { 370 | forceVector.resize(this.MAX_FORCES); 371 | } 372 | } 373 | else 374 | { 375 | forceVector.set((me.x - him.x) * buddiness, (me.y - him.y) * buddiness); 376 | if (forceVector.length() > this.MAX_FORCES) 377 | { 378 | forceVector.resize(0); 379 | } 380 | else if(forceVector.length() > 0) 381 | { 382 | forceVector.resize(this.MAX_FORCES - forceVector.length()); 383 | } 384 | } 385 | 386 | sumForces.addIn(forceVector); 387 | } 388 | 389 | sumForces.addIn( 390 | new jssim.Vector2D((this.yard.width * 0.5 - me.x) * this.forceToSchoolMultiplier, (this.yard.height * 0.5 - me.y) * this.forceToSchoolMultiplier) 391 | ); 392 | 393 | sumForces.addIn( 394 | new jssim.Vector2D(this.randomMultiplier * (Math.random() * 1.0 - 0.5), this.randomMultiplier * Math.random() * 1.0 - 0.5)); 395 | 396 | sumForces.addIn(me); 397 | 398 | me.x = sumForces.x; 399 | me.y = sumForces.y; 400 | 401 | console.log("Student " + this.id + " is at (" + me.x + ", " + me.y + ") at time " + this.time); 402 | }; 403 | 404 | var scheduler = new jssim.Scheduler(); 405 | var yard = new jssim.Space2D(); 406 | var network = new jssim.Network(30); 407 | yard.width = 50; 408 | yard.height = 50; 409 | 410 | scheduler.reset(); 411 | yard.reset(); 412 | network.reset(); 413 | for(var i=0; i < 30; ++i) { 414 | var student = new Student(i, yard, network); 415 | yard.updateAgent(student, Math.random() * yard.width, Math.random() * yard.height); 416 | scheduler.scheduleRepeatingIn(student, 1); 417 | } 418 | 419 | var buddies = {}; 420 | for (var i = 0; i < 30; ++i) 421 | { 422 | var student = i; 423 | var studentB = i; 424 | do 425 | { 426 | studentB = Math.floor(Math.random() * 30); 427 | } while (student == studentB); 428 | var buddiness = Math.random(); 429 | if(!network.connected(student, studentB)){ 430 | network.addEdge(new jssim.Edge(student, studentB, buddiness)); 431 | } 432 | 433 | var studentB = i; 434 | do 435 | { 436 | studentB = Math.floor(Math.random() * 30); 437 | } while (student == studentB); 438 | 439 | buddiness = Math.random(); 440 | if(!network.connected(student, studentB)){ 441 | network.addEdge(new jssim.Edge(student, studentB, buddiness)); 442 | } 443 | } 444 | 445 | while (scheduler.current_time < 2) { 446 | scheduler.update(); 447 | } 448 | ``` 449 | 450 | 451 | 452 | -------------------------------------------------------------------------------- /build/jssim.min.js: -------------------------------------------------------------------------------- 1 | var jssim=jssim||{};!function(t){t.exchange=function(t,i,e){var s=t[i];t[i]=t[e],t[e]=s},t.shuffle=function(i){for(var e=i.length,s=1;s1;){var e=Math.floor(i/2);if(!this.less(this.s[i],this.s[e]))break;t.exchange(this.s,i,e),i=e}},i.prototype.clear=function(){this.s=[],this.N=0},i.prototype.size=function(){return this.N},i.prototype.isEmpty=function(){return 0==this.N},t.MinPQ=i;var e=function(t){this.value=t,this.next=null};t.StackNode=e;var s=function(){this.N=0,this.first=null};s.prototype.push=function(i){var e=this.first;this.first=new t.StackNode(i),this.first.next=e,this.N++},s.prototype.pop=function(t){var i=this.first;if(null==i)return null;var t=i.value;return this.first=i.next,this.N--,t},s.prototype.clear=function(){this.N=0,this.first=null},s.prototype.size=function(){return this.N},s.prototype.isEmpty=function(){return 0==this.N},t.Stack=s;var h=function(t){this.value=t,this.next=null};t.QueueNode=h;var n=function(){this.first=null,this.last=null,this.N=0};n.prototype.enqueue=function(i){var e=this.last;this.last=new t.QueueNode(i),null!=e&&(e.next=this.last),null==this.first&&(this.first=this.last),this.N++},n.prototype.dequeue=function(){var t=this.first;if(null==t)return null;var i=t.value;return this.first=t.next,null==this.first&&(this.last=null),this.N--,i},n.prototype.clear=function(){this.first=null,this.last=null,this.N=0},n.prototype.isEmpty=function(){return 0==this.N},n.prototype.size=function(){return this.N},t.Queue=n;var r=function(t){this.time=0,this.rank=t||1};r.prototype.guid=function(){return this.id?this.id:(this.id=t.guid(),this.id)},r.prototype.sendMsg=function(t,i){this.scheduler.messenger.sendMsg(this.guid(),t,i)},r.prototype.readInBox=function(){return this.scheduler.messenger.readInBox(this.guid())},t.SimEvent=r;var o=function(){this.pq=new t.MinPQ(function(t,i){var e=t.time-i.time;return 0==e?i.rank-t.rank:e}),this.current_time=null,this.current_rank=null,this.messenger=new t.Messenger(this)};o.prototype.update=function(){for(var t=null;!this.pq.isEmpty();){var i=this.pq.min(),e=i.time;i.rank;if(null==t)t=e;else if(t!=e)break;this.update_mini()}this.messenger.update(0)},o.prototype.update_mini=function(){for(var i=null,e=null,s=[];!this.pq.isEmpty();){var h=this.pq.min(),n=h.time,r=h.rank;if(null==i)i=n;else if(i!=n)break;if(null==e)e=r;else if(e!=r)break;s.push(this.pq.delMin())}t.shuffle(s);var o=this.current_time;s.length>0&&(this.current_time=i,this.current_rank=e);for(a=0;a0&&(this.trails[t][i]+=1)},c.prototype.setObstable=function(t,i,e){this.obstacles[t][i]=e},c.prototype.setPotential=function(t,i,e){this.potentialField[t][i]=e,e>this.maxPotential&&(this.maxPotential=e),e=this.width&&(h=this.width-1),n<0&&(n=0),r>=this.height&&(r=this.height-1);for(var o=s;o<=h;++o)for(var l=n;l<=r;++l){var a=o-t,u=l-i;Math.sqrt(a*a+u*u)<=e&&(this.obstacles[o][l]=1)}},c.prototype.createTarget=function(t,i,e){for(var s=Math.floor(e/2),h=t-s,n=h+e,r=i-s,o=r+e,l=h;l<=n;++l)for(var a=r;a<=o;++a)this.targets[l][a]=1},c.prototype.getCell=function(t,i){return this.cells[t][i]},c.prototype.makeCopy=function(){for(var t=new c(this.width,this.height),i=0;i=this.width)&&(!(i<0||i>=this.height)&&this.cells[t][i]>0)},c.prototype.isObstacle=function(t,i){return!(t<0||t>=this.width)&&(!(i<0||i>=this.height)&&this.obstacles[t][i]>0)},c.prototype.isTarget=function(t,i){return!(t<0||t>=this.width)&&(!(i<0||i>=this.height)&&this.targets[t][i]>0)},c.prototype.render=function(t){if(t){var i=t.getContext("2d");i.clearRect(0,0,t.width,t.height),i.fillStyle=this.color;for(var e=0;e0&&(i.fillStyle=this.trailColor,i.fillRect(e*this.cellWidth,s*this.cellHeight,this.cellWidth-1,this.cellHeight-1)),this.showPotentialField&&0!=this.potentialField[e][s]){var h=this.potentialField[e][s],n=100+Math.floor(155*(h-this.minPotential)/(this.maxPotential-this.minPotential)),r=Math.floor(255*(h-this.minPotential)/(this.maxPotential-this.minPotential));i.fillStyle="rgb(255,"+n+","+r+")",i.fillRect(e*this.cellWidth,s*this.cellHeight,this.cellWidth-1,this.cellHeight-1)}this.obstacles[e][s]>0&&(i.fillStyle=this.obstacleColor,i.fillRect(e*this.cellWidth,s*this.cellHeight,this.cellWidth-1,this.cellHeight-1)),this.cells[e][s]>0&&(i.fillStyle=this.color,i.fillRect(e*this.cellWidth,s*this.cellHeight,this.cellWidth-1,this.cellHeight-1)),this.targets[e][s]>0&&(i.fillStyle=this.targetColor,i.fillRect(e*this.cellWidth,s*this.cellHeight,this.cellWidth-1,this.cellHeight-1))}}},t.Grid=c;var p=function(t,i,e){this.v=t,this.w=i,this.info=e};p.prototype.other=function(t){return t==this.v?this.w:this.v},p.prototype.either=function(){return this.v},t.Edge=p;var f=function(t){this.V=t,this.adjList=[];for(var i=0;i10)for(var e=i.min();null!=e&&e.time 2 | 3 | Discrete-Event Simulator: Ant System 4 | 5 | 6 | 7 |

Discrete-Event Simulator: Ant System

8 | 9 | 10 | 11 | 12 | 262 | 263 | -------------------------------------------------------------------------------- /examples/example-balls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Discrete-Event Simulator: Balls 4 | 5 | 6 | 7 |

Discrete-Event Simulator: Balls

8 | 9 |

10 | A demonstration of Hooke's Law. Balls of random location and mass are connected arbitrarily with rubber bands of random strength and minimum lax distance. 11 |

12 | 13 | 14 | 15 | 16 | 17 | 232 | 233 | -------------------------------------------------------------------------------- /examples/example-dragon-curve.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Discrete-Event Simulator: L-System (Dragon Curve) 4 | 5 | 6 | 7 |

Discrete-Event Simulator: L-System (Dragon Curve)

8 | 9 | 10 | 11 | 12 | 156 | 157 | -------------------------------------------------------------------------------- /examples/example-flocking.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Discrete-Event Simulator: Flocking 4 | 5 | 6 | 7 |

Discrete-Event Simulator: Flocking Boids

8 | 9 | 10 | 11 | 12 | 157 | 158 | -------------------------------------------------------------------------------- /examples/example-game-of-life.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Discrete-Event Simulator: Game of Life 4 | 5 | 6 | 7 |

Discrete-Event Simulator: Conway's Game of Life

8 |

Game of life will restart randomly every 40 seconds :)

9 | 10 | 11 | 12 | 13 | 99 | 100 | -------------------------------------------------------------------------------- /examples/example-l-system.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Discrete-Event Simulator: L-System (Fractal Plant) 4 | 5 | 6 | 7 |

Discrete-Event Simulator: L-System (Fractal Plant)

8 | 9 | 10 | 11 | 12 | 157 | 158 | -------------------------------------------------------------------------------- /examples/example-near-beer-game.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Discrete-Event Simulator: Near Beer Game 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |

Discrete-Event Simulator: Near Beer Game

22 |
23 |
24 |
25 |
26 |
27 | 28 |
Customer
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
WeekOrderingReceivedUnfulfilled
{{record.weeks}}{{record.order}}{{record.received}}{{record.totalOrder}}
48 |
49 |
50 |
51 | 52 |
53 |
54 | 55 |
Near Beer
56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
WeekBrewed BeersInventory StockOrderingBacklogShipment
{{record.weeks}}{{record.beers}}{{record.inventoryOnHand}}{{record.newRawMaterialOrder}}{{record.backlog}}{{record.shipment}}
79 |
80 |
81 |
82 | 83 | 84 | 85 |
86 |
87 | 88 |
Supplier
89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 |
WeekProduceShipmentStocksTotal Shipment
{{record.weeks}}{{record.produced}}{{record.shipment}}{{record.inventoryOnHand}}{{record.totalShipment}}
110 |
111 |
112 |
113 | 114 | 115 |
116 | 117 |
118 |
119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 141 | 153 | 165 | 166 | 167 |
Customer InboxNear Bear InboxSuppler Inbox
130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 |
SenderMessageDetail
{{msg.sender}}{{msg.type}}Quantity is {{msg.quantity}}
140 |
142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 |
SenderMessageDetail
{{msg.sender}}{{msg.type}}Quantity is {{msg.quantity}}
152 |
154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |
SenderMessageDetail
{{msg.sender}}{{msg.type}}Quantity is {{msg.quantity}}
164 |
168 |
169 |
170 |
171 | 172 | 410 | 411 | -------------------------------------------------------------------------------- /examples/example-pso.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Discrete-Event Simulator: Particle Swarm 4 | 5 | 6 | 7 |

Discrete-Event Simulator: Particle Swarm

8 | 9 |

10 | Particle Swarm Optimization (PSO) is a population-oriented stochastic search technique similar to genetic algorithms, evolutionary strategies, and other evolutionary computation algorithms. The technique discovers solutions for N-dimensional parameterized problems: basically it discovers the point in N-dimensional space which maximizes some quality function. 11 |

12 | 13 |

14 | PSO works approximately as follows: a "swarm" of candidate solutions ("particles") are placed randomly throughout the space and with a random initial velocity. Each particle measures the quality of its present location in space. This velocity vector affects how each particle moves in the space, and it is changed each timestep according to a weighted sum of the following values: 15 |

16 | 17 |
    18 |
  • A vector towards the personal best-quality location the particle has been.
  • 19 |
  • A vector towards the global best-quality location any member of the swarm has ever visited.
  • 20 |
  • A vector towards the "neighborhood best": all the particles are organized beforehand in a list. A particle's neighborhood are himself and the M individuals on either side of him. The neighborhood best is the best-quality location any neighbor ever visited.
  • 21 |
  • The particle's previous velocity vector.
  • 22 |
23 | 24 |

The demo visualize the PSO running on the cost function Rastrigin.

25 | 26 | 27 |

28 | 29 | 30 | 31 | 32 |

33 |

The potential field is a Rastrigin function. Particle Swarm will restart randomly every 40 seconds :)

34 | 35 | 36 | 37 | 38 | 198 | 199 | -------------------------------------------------------------------------------- /examples/example-school-yard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Discrete-Event Simulator: School Yard 4 | 5 | 6 | 7 |

Discrete-Event Simulator: School Yard

8 | 9 | 10 | 11 | 12 | 139 | 140 | -------------------------------------------------------------------------------- /examples/example-solar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Discrete-Event Simulator: Solar System 4 | 5 | 6 | 7 |

Discrete-Event Simulator: Solar System

8 | 9 | 10 |

11 | Some planetary orbits; To improve the animation, the diameter and distances as well as period are scaled. 12 |

13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 115 | 116 | -------------------------------------------------------------------------------- /examples/example-virus.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Discrete-Event Simulator: Virus 4 | 5 | 6 | 7 |

Discrete-Event Simulator: Virus

8 | 9 |

10 | A simulation of intentional virus infection and disinfection in a population. The bad guys (red) either move randomly or greedily chase after the nearest uninfected individual (in orange). Infected individuals (in purple) move randomly and may be disinfected by good guys (in green) who likewise eeither move randomly or greedily. 11 |

12 | 13 | 14 | 15 | 16 | 17 | 18 | 394 | 395 | -------------------------------------------------------------------------------- /examples/good.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/js-simulator/324435b46b0e060b4b94124466c0b363914a73c3/examples/good.png -------------------------------------------------------------------------------- /examples/infected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/js-simulator/324435b46b0e060b4b94124466c0b363914a73c3/examples/infected.png -------------------------------------------------------------------------------- /examples/normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/js-simulator/324435b46b0e060b4b94124466c0b363914a73c3/examples/normal.png -------------------------------------------------------------------------------- /examples/virus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen0040/js-simulator/324435b46b0e060b4b94124466c0b363914a73c3/examples/virus.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/jssim'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-simulator", 3 | "version": "1.0.18", 4 | "description": "General-purpose discrete-event simulator for agent-based modelling and simulation", 5 | "author": "Xianshun Chen", 6 | "contributors": [ 7 | "Xianshun Chen " 8 | ], 9 | "license": "MIT", 10 | "main": "index.js", 11 | "directories": { 12 | "test": "test" 13 | }, 14 | "scripts": { 15 | "test": "mocha test", 16 | "cover": "istanbul cover _mocha", 17 | "coveralls": "npm run cover -- --report lcovonly && cat ./coverage/lcov.info | coveralls" 18 | }, 19 | "bin": { 20 | "js-simulator": "./src/jssim.js" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/chen0040/js-simulator.git" 25 | }, 26 | "keywords": [ 27 | "discrete", 28 | "event", 29 | "simulator", 30 | "multiagent", 31 | "multi-agent", 32 | "discrete-event-simulation", 33 | "simulation", 34 | "abms", 35 | "agent", 36 | "modelling and simulation", 37 | "agent-based modelling and simulation", 38 | "intelligent agent", 39 | "agent-modelling", 40 | "flocking", 41 | "L system", 42 | "fractal", 43 | "particle swarm", 44 | "ant system", 45 | "school yard", 46 | "game of life", 47 | "simulation engine" 48 | ], 49 | "dependencies": { 50 | 51 | }, 52 | "devDependencies": { 53 | "chai": "^3.5.0", 54 | "coveralls": "^2.13.1", 55 | "grunt": "^1.0.1", 56 | "grunt-contrib-uglify": "^3.0.0", 57 | "grunt-eslint": "^19.0.0", 58 | "grunt-mocha-test": "^0.13.2", 59 | "istanbul": "^0.4.5", 60 | "mocha": "^3.4.1" 61 | }, 62 | "bugs": { 63 | "url": "https://github.com/chen0040/js-simulator/issues" 64 | }, 65 | "homepage": "https://github.com/chen0040/js-simulator#readme" 66 | } 67 | -------------------------------------------------------------------------------- /test/ant-spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jssim = require('../src/jssim'); 3 | 4 | describe('Ant System', function(){ 5 | it('should be able to automatically find the shortest path to reach target using pheromones', function(){ 6 | var target_x = 110; 7 | var target_y = 40; 8 | var tiles = 128; 9 | var tau_0 = 1.0 / (tiles * tiles); 10 | 11 | var Evaporator = function(pheromones) { 12 | jssim.SimEvent.call(this, 1); 13 | this.pheromones = pheromones; 14 | }; 15 | 16 | Evaporator.prototype = Object.create(jssim.SimEvent); 17 | 18 | Evaporator.prototype.update = function(deltaTime) { 19 | for(var x = 0; x < tiles; ++x) { 20 | for(var y = 0; y < tiles; ++y) { 21 | var t = this.pheromones.getCell(x, y); 22 | t = 0.9 * t; 23 | if (t < tau_0) { 24 | t = tau_0; 25 | } 26 | this.pheromones.setCell(x, y, t); 27 | } 28 | } 29 | }; 30 | 31 | var Ant = function(id, grid, pheromones, x, y) { 32 | jssim.SimEvent.call(this, 2); 33 | this.id = id; 34 | this.x = x; 35 | this.y = y; 36 | this.init_x = x; 37 | this.init_y = y; 38 | this.prev_x = x; 39 | this.prev_y = y; 40 | this.grid = grid; 41 | this.pheromones = pheromones; 42 | this.grid.setCell(this.x, this.y, 1); 43 | this.path = []; 44 | this.age = 0; 45 | this.life = 150; 46 | }; 47 | Ant.prototype = Object.create(jssim.SimEvent); 48 | 49 | Ant.prototype.reset = function () { 50 | this.grid.setCell(this.x, this.y, 0); 51 | this.x = this.init_x; 52 | this.y = this.init_y; 53 | this.prev_x = this.x; 54 | this.prev_y = this.y; 55 | this.grid.setCell(this.x, this.y, 1); 56 | this.age = 0; 57 | this.path = []; 58 | }; 59 | 60 | Ant.prototype.depositPheromones = function () { 61 | for(var i = 0; i < this.path.length; ++i) { 62 | var move = this.path[i]; 63 | this.pheromones.setCell(move.x, move.y, this.pheromones.getCell(move.x, move.y) + 1.0 / this.path.length); 64 | } 65 | }; 66 | 67 | Ant.prototype.getCandidateMoves = function () { 68 | var candidates = []; 69 | 70 | for(var dx = -1; dx <= 1; ++dx) { 71 | for(var dy = -1; dy <= 1; ++dy) { 72 | var _x = this.x + dx; 73 | var _y = this.y + dy; 74 | if(_x == this.prev_x && _y == this.prev_y) continue; 75 | if(_x == this.x && _y == this.y) continue; 76 | if(this.grid.isObstacle(_x, _y)) { 77 | continue; 78 | } 79 | if(this.grid.isOccupied(_x, _y)) { 80 | continue; 81 | } 82 | if(this.grid.isTarget(_x, _y)) { 83 | this.depositPheromones(); 84 | this.reset(); 85 | return candidates; 86 | } 87 | 88 | candidates.push({ 89 | x: _x, 90 | y: _y 91 | }); 92 | } 93 | } 94 | return candidates; 95 | }; 96 | 97 | Ant.prototype.selectMove = function (candidates) { 98 | var heuristics = []; 99 | 100 | var dx2 = target_x - this.x; 101 | var dy2 = target_y - this.y; 102 | var dlen2 = Math.sqrt(dx2 * dx2 + dy2 * dy2); 103 | for(var i = 0; i < candidates.length; ++i) { 104 | var move = candidates[i]; 105 | var dx = move.x - this.x; 106 | var dy = move.y - this.y; 107 | var dlen = Math.sqrt(dx * dx + dy * dy); 108 | 109 | var heuristic_b = ((dx * dx2 + dy * dy2) / (dlen * dlen2) + 1) / (tiles * tiles); 110 | var heuristic_a = this.pheromones.getCell(move.x, move.y); 111 | var heuristic = heuristic_a * Math.pow(heuristic_b, 2.0); 112 | 113 | heuristics.push(heuristic); 114 | } 115 | 116 | 117 | 118 | var r = Math.random(); 119 | 120 | if(r < 0.9) { 121 | // Exploitation 122 | var max_i = -1; 123 | var max_heuristic = 0; 124 | for(var i=0; i < candidates.length; ++i) { 125 | if(heuristics[i] > max_heuristic) { 126 | max_heuristic = heuristics[i]; 127 | max_i = i; 128 | } 129 | } 130 | return max_i; 131 | } else { 132 | // Exploration 133 | r = Math.random(); 134 | var heuristic_sum = 0; 135 | for(var i = 0; i < candidates.length; ++i) { 136 | heuristic_sum += heuristics[i]; 137 | heuristics[i] = heuristic_sum; 138 | } 139 | for(var i = 0; i < candidates.length; ++i) { 140 | heuristics[i] /= heuristic_sum; 141 | } 142 | 143 | for(var i = 0; i < candidates.length; ++i) { 144 | if(r <= heuristics[i]) { 145 | return i; 146 | } 147 | } 148 | } 149 | return -1; 150 | } 151 | 152 | Ant.prototype.update = function(deltaTime) { 153 | 154 | this.age++; 155 | 156 | if(this.age >= this.life) { 157 | this.reset(); 158 | } 159 | 160 | 161 | 162 | var candidates = this.getCandidateMoves(); 163 | 164 | if(candidates.length == 0) return; 165 | 166 | var max_i = this.selectMove(candidates); 167 | 168 | var act_x = this.x; 169 | var act_y = this.y; 170 | 171 | if(max_i != -1){ 172 | act_x = candidates[max_i].x; 173 | act_y = candidates[max_i].y; 174 | this.path.push(candidates[max_i]); 175 | } 176 | 177 | this.moveTo(act_x, act_y); 178 | 179 | }; 180 | 181 | Ant.prototype.moveTo = function(act_x, act_y) { 182 | this.grid.setCell(this.x, this.y, 0); 183 | this.prev_x = this.x; 184 | this.prev_y = this.y; 185 | this.x = act_x; 186 | this.y = act_y; 187 | this.grid.setCell(this.x, this.y, 1); 188 | }; 189 | 190 | var scheduler = new jssim.Scheduler(); 191 | 192 | var grid = new jssim.Grid(tiles, tiles); 193 | grid.cellWidth = 5; 194 | grid.cellHeight = 5; 195 | grid.showTrails = true; 196 | 197 | var pheromones = new jssim.Grid(tiles, tiles); 198 | pheromones.cellWidth = 5; 199 | pheromones.cellHeight = 5; 200 | 201 | 202 | function reset() { 203 | scheduler.reset(); 204 | grid.reset(); 205 | pheromones.reset(); 206 | 207 | grid.createCylinder(50, 80, 20); 208 | grid.createCylinder(30, 100, 10); 209 | grid.createCylinder(80, 50, 21); 210 | grid.createCylinder(80, 28, 11); 211 | grid.createCylinder(75, 35, 11); 212 | 213 | grid.createCylinder(103, 26, 11); 214 | 215 | for(var x= 0; x < tiles; ++x){ 216 | for(var y = 0; y 1; // other than myself of course 62 | }; 63 | 64 | Ball.prototype.addForce = function(otherBallLoc, myLoc, band) { 65 | // compute difference 66 | var dx = otherBallLoc.x - myLoc.x; 67 | var dy = otherBallLoc.y - myLoc.y; 68 | var len = Math.sqrt(dx*dx + dy*dy); 69 | var l = band.laxDistance; 70 | 71 | var k = band.strength / 512.0; // cut-down 72 | var forcemagnitude = (len - l) * k; 73 | 74 | // add rubber band force 75 | if (len - l > 0) 76 | { 77 | this.forcex += (dx * forcemagnitude) / len; 78 | this.forcey += (dy * forcemagnitude) / len; 79 | } 80 | }; 81 | 82 | Ball.prototype.computeForce = function() { 83 | 84 | var me = this.space.getLocation(this.id); 85 | 86 | this.forcex = 0; 87 | this.forcey = 0; 88 | // rubber bands exert a force both ways -- 89 | // so our graph is undirected. 90 | var adj_v = this.bands.adj(this.id); 91 | 92 | for(var x=0; x < adj_v.length;x++) 93 | { 94 | var e = adj_v[x]; 95 | var b = e.info; 96 | var other = e.other(this.id); // from him to me 97 | var him = this.space.getLocation(other); 98 | this.addForce(him, me, b); 99 | 100 | var otherBall = this.space.getAgent(other); 101 | otherBall.addForce(me, him, b); 102 | } 103 | }; 104 | 105 | Ball.prototype.update = function(deltaTime) { 106 | // acceleration = force / mass 107 | var ax = this.forcex / this.mass; 108 | var ay = this.forcey / this.mass; 109 | 110 | 111 | // velocity = velocity + acceleration 112 | this.velocityx += ax; 113 | this.velocityy += ay; 114 | 115 | 116 | // position = position + velocity 117 | var pos = this.space.getLocation(this.id); 118 | var newpos = new jssim.Vector2D(pos.x+ this.velocityx, pos.y + this.velocityy); 119 | this.space.updateAgent(this, newpos.x, newpos.y); 120 | 121 | 122 | // compute collisions 123 | this.computeCollision(); 124 | }; 125 | 126 | Ball.prototype.draw = function(context, pos) { 127 | context.fillStyle="#000000"; 128 | if(this.collision) { 129 | context.fillStyle = '#ff0000'; 130 | } else { 131 | context.fillStyle = '#00ff00'; 132 | } 133 | var size = 20; 134 | 135 | //context.fillRect(pos.x, worldHeight - pos.y, width, height); 136 | context.beginPath(); 137 | context.arc(pos.x, pos.y, size, 0, 2 * Math.PI); 138 | context.fill(); 139 | 140 | context.fillStyle = '#ffffff'; 141 | 142 | context.font = "12 Arial"; 143 | context.fillText("" + this.id,pos.x, pos.y); 144 | 145 | var me = this.space.getLocation(this.id); 146 | var adj_me = this.bands.adj(this.id); 147 | for(var i=0; i < adj_me.length; ++i) { 148 | var e = adj_me[i]; 149 | var band = e.info; 150 | var other = this.space.getLocation(e.other(this.id)); 151 | 152 | var distance = me.distance(other); 153 | if(distance > band.laxDistance) { 154 | context.strokeStyle = "#cccccc"; 155 | context.beginPath(); 156 | context.moveTo(me.x, worldHeight - me.y); // y coordindate is inverted 157 | context.lineTo(other.x, worldHeight - other.y); // y coordindate is inverted 158 | context.stroke(); 159 | } 160 | } 161 | }; 162 | 163 | var scheduler = new jssim.Scheduler(); 164 | 165 | var space = new jssim.Space2D(); 166 | var bands = new jssim.Network(numBalls); 167 | var s = []; 168 | 169 | 170 | function reset() { 171 | scheduler.reset(); 172 | space.reset(); 173 | bands.reset(); 174 | s = []; 175 | 176 | 177 | // make the balls 178 | for(var i=0; i distance){ 43 | min_distance = distance; 44 | prey = boid; 45 | } 46 | } else { 47 | var boid_pos = this.space.getLocation(boid.id); 48 | var distance = pos.distance(boid_pos); 49 | if (distance < this.separation_space) 50 | { 51 | // Separation 52 | this.velocity.x += pos.x - boid_pos.x; 53 | this.velocity.y += pos.y - boid_pos.y; 54 | } 55 | } 56 | } 57 | 58 | if(prey != null) { 59 | var prey_position = this.space.getLocation(prey.id); 60 | this.velocity.x += prey_position.x - pos.x; 61 | this.velocity.y += prey_position.y - pos.y; 62 | } 63 | 64 | } else { 65 | for (var boidId in boids) 66 | { 67 | var boid = boids[boidId]; 68 | var boid_pos = this.space.getLocation(boid.id); 69 | var distance = pos.distance(boid_pos); 70 | if (boid != this && !boid.isPredator) 71 | { 72 | if (distance < this.separation_space) 73 | { 74 | // Separation 75 | this.velocity.x += pos.x - boid_pos.x; 76 | this.velocity.y += pos.y - boid_pos.y; 77 | } 78 | else if (distance < this.sight) 79 | { 80 | // Cohesion 81 | this.velocity.x += (boid_pos.x - pos.x) * 0.05; 82 | this.velocity.y += (boid_pos.y - pos.y) * 0.05; 83 | } 84 | if (distance < this.sight) 85 | { 86 | // Alignment 87 | this.velocity.x += boid.velocity.x * 0.5; 88 | this.velocity.y += boid.velocity.y * 0.5; 89 | } 90 | } 91 | if (boid.isPredator && distance < this.sight) 92 | { 93 | // Avoid predators. 94 | this.velocity.x += pos.x - boid_pos.x; 95 | this.velocity.y += pos.y - boid_pos.y; 96 | } 97 | } 98 | } 99 | 100 | 101 | 102 | 103 | // check speed 104 | var speed = this.velocity.length(); 105 | if(speed > this.speed) { 106 | this.velocity.resize(this.speed); 107 | } 108 | 109 | 110 | 111 | 112 | pos.x += this.velocity.x; 113 | pos.y += this.velocity.y; 114 | 115 | // check boundary 116 | var val = this.boundary - this.border; 117 | if (pos.x < this.border) pos.x = this.boundary - this.border; 118 | if (pos.y < this.border) pos.y = this.boundary - this.border; 119 | if (pos.x > val) pos.x = this.border; 120 | if (pos.y > val) pos.y = this.border; 121 | 122 | 123 | console.log("boid [ " + this.id + "] is at (" + pos.x + ", " + pos.y + ") at time " + this.time); 124 | }; 125 | 126 | var scheduler = new jssim.Scheduler(); 127 | scheduler.reset(); 128 | 129 | var space = new jssim.Space2D(); 130 | space.reset(); 131 | 132 | for(var i = 0; i < 30; ++i) { 133 | var is_predator = i > 27; 134 | var boid = new Boid(i, 0, 0, space, is_predator); 135 | scheduler.scheduleRepeatingIn(boid, 1); 136 | } 137 | 138 | 139 | while(scheduler.current_time < 20) { 140 | scheduler.update(); 141 | } 142 | 143 | 144 | }); 145 | }); -------------------------------------------------------------------------------- /test/game-of-life-spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jssim = require('../src/jssim'); 3 | 4 | describe('Game of Life', function() { 5 | it('should generate and kill cell based on the 8 surrounding cells', function(){ 6 | var CellularAgent = function(world) { 7 | jssim.SimEvent.call(this); 8 | this.world = world; 9 | }; 10 | 11 | CellularAgent.prototype = Object.create(jssim.SimEvent.prototype); 12 | CellularAgent.prototype.update = function (deltaTime) { 13 | var width = this.world.width; 14 | var height = this.world.height; 15 | var past_grid = this.world.makeCopy(); 16 | for(var i=0; i < width; ++i) { 17 | for(var j = 0; j < height; ++j) { 18 | var count = 0; 19 | for(var dx = -1; dx < 2; ++dx) { 20 | var x = i + dx; 21 | if (x >= width) { 22 | x = 0; 23 | } 24 | if (x < 0) { 25 | x = width - 1; 26 | } 27 | for(var dy = -1; dy < 2; ++dy) { 28 | var y = j + dy; 29 | if(y >= height) { 30 | y = 0; 31 | } 32 | if(y < 0) { 33 | y = height - 1; 34 | } 35 | count += past_grid.getCell(x, y); 36 | } 37 | } 38 | if (count <= 2 || count >= 5) { 39 | this.world.setCell(i, j, 0); // dead 40 | } 41 | if (count == 3) { 42 | this.world.setCell(i, j, 1); // live 43 | } 44 | } 45 | } 46 | }; 47 | 48 | var scheduler = new jssim.Scheduler(); 49 | var grid = new jssim.Grid(64, 64); 50 | 51 | scheduler.reset(); 52 | grid.reset(); 53 | 54 | grid.setCell(1, 0, 1); 55 | grid.setCell(2, 0, 1); 56 | grid.setCell(0, 1, 1); 57 | grid.setCell(1, 1, 1); 58 | grid.setCell(1, 2, 1); 59 | grid.setCell(2, 2, 1); 60 | grid.setCell(2, 3, 1); 61 | 62 | scheduler.scheduleRepeatingIn(new CellularAgent(grid), 1); 63 | 64 | while(scheduler.current_time < 5) { 65 | scheduler.update(); 66 | } 67 | }); 68 | }); -------------------------------------------------------------------------------- /test/l-system-spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jssim = require('../src/jssim'); 3 | 4 | describe('L-System', function(){ 5 | it('should generate the fractal plant', function(){ 6 | var Pen = function(x, y, angle) { 7 | this.x = x; 8 | this.y = y; 9 | this.angle = angle; 10 | this.init_x = x; 11 | this.init_y = y; 12 | this.init_angle = angle; 13 | this.stack = new jssim.Stack(); 14 | this.canvas = new jssim.Space2D(); 15 | this.commands = new jssim.Queue(); 16 | }; 17 | 18 | Pen.prototype.reset = function () { 19 | this.x = this.init_x; 20 | this.y = this.init_y; 21 | this.angle = this.init_angle; 22 | this.canvas.reset(); 23 | this.stack.clear(); 24 | this.commands.clear(); 25 | }; 26 | 27 | Pen.prototype.saveState = function () { 28 | this.stack.push(new Pen(this.x, this.y, this.angle)); 29 | }; 30 | 31 | Pen.prototype.loadState = function () { 32 | var oldState = this.stack.pop(); 33 | this.x = oldState.x; 34 | this.y = oldState.y; 35 | this.angle = oldState.angle; 36 | }; 37 | 38 | Pen.prototype.rotateLeft = function (angle) { 39 | this.angle += angle; 40 | }; 41 | 42 | Pen.prototype.rotateRight = function (angle) { 43 | this.angle -= angle; 44 | }; 45 | 46 | Pen.prototype.pushCommand = function(symbol) { 47 | this.commands.enqueue(symbol); 48 | }; 49 | 50 | Pen.prototype.popCommand = function(symbol) { 51 | return this.commands.dequeue(); 52 | }; 53 | 54 | Pen.prototype.drawForward = function (stride) { 55 | var rad = this.angle * Math.PI / 180; 56 | var dx = Math.cos(rad) * stride; 57 | var dy = Math.sin(rad) * stride; 58 | var oldX = this.x; 59 | var oldY = this.y; 60 | this.x += dx; 61 | this.y += dy; 62 | this.canvas.drawLine(oldX, oldY, this.x, this.y); 63 | }; 64 | 65 | Pen.prototype.hasMoreCommands = function () { 66 | return !this.commands.isEmpty(); 67 | }; 68 | 69 | Pen.prototype.clearCanvas = function () { 70 | this.canvas.clearLines(); 71 | }; 72 | 73 | var Turtle = function(scheduler, pen) { 74 | jssim.SimEvent.call(this); 75 | this.scheduler = scheduler; 76 | this.pen = pen; 77 | this.angle = 25; 78 | this.stride = 1; 79 | }; 80 | Turtle.prototype = Object.create(jssim.SimEvent.prototype); 81 | Turtle.prototype.update = function(deltaTime){ 82 | // (X → F[−X][X]F[−X]+FX), (F → FF) 83 | var rules = ''; 84 | 85 | 86 | while(this.pen.hasMoreCommands()){ 87 | var symbol = this.pen.popCommand(); 88 | 89 | var rule = ''; 90 | if(symbol == 'X') { 91 | rule = 'F[−X][X]F[−X]+FX'; 92 | } else if(symbol == 'F'){ 93 | rule = 'FF'; 94 | } else { 95 | rule = symbol; 96 | } 97 | 98 | rules += rule; 99 | } 100 | 101 | this.pen.reset(); 102 | for(var i = 0; i < rules.length; ++i) { 103 | var symbol = rules[i]; 104 | if(symbol == 'F') { 105 | this.pen.drawForward(this.stride); 106 | } else if(symbol == '[') { 107 | this.pen.saveState(); 108 | } else if(symbol == ']') { 109 | this.pen.loadState(); 110 | } else if(symbol == '-') { 111 | this.pen.rotateLeft(this.angle); 112 | } else if(symbol == '+') { 113 | this.pen.rotateRight(this.angle); 114 | } 115 | pen.pushCommand(rules[i]); 116 | } 117 | 118 | //console.log('state at time ' + this.time + ': ' + rules); 119 | }; 120 | 121 | var scheduler = new jssim.Scheduler(); 122 | var pen = new Pen(10, 0, 60); 123 | var turtle = new Turtle(scheduler, pen); 124 | turtle.stride = 5; 125 | 126 | 127 | scheduler.reset(); 128 | pen.reset(); 129 | 130 | pen.pushCommand('X'); 131 | scheduler.scheduleRepeatingIn(turtle, 1); 132 | 133 | 134 | while(scheduler.hasEvents() && scheduler.current_time < 3) { 135 | scheduler.update(); 136 | } 137 | 138 | 139 | 140 | 141 | }); 142 | }); -------------------------------------------------------------------------------- /test/messenger-spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jssim = require('../src/jssim'); 3 | 4 | describe('Messenger', function(){ 5 | it('should delete messages when the inbox grows larger than 10', function(){ 6 | var scheduler = new jssim.Scheduler(); 7 | var repeated = new jssim.SimEvent(); 8 | repeated.id = 1; 9 | repeated.update = function(deltaTime) { 10 | for(var i=0; i < 100; ++i){ 11 | this.sendMsg(2, { 12 | content: 'message that no body tries to read' 13 | }); 14 | } 15 | }; 16 | 17 | scheduler.scheduleRepeatingIn(repeated, 1); 18 | 19 | while(scheduler.current_time < 20) { 20 | scheduler.update(); 21 | console.log('inbox: ' + scheduler.messenger.inbox[2].size()); 22 | expect(scheduler.messenger.inbox[2].size()).to.equal(100); 23 | } 24 | }); 25 | }); -------------------------------------------------------------------------------- /test/min-pq-spec.js: -------------------------------------------------------------------------------- 1 | var expect = require("chai").expect; 2 | var jssim = require("../src/jssim"); 3 | 4 | describe("MinPQ", function() { 5 | it("should operate correctly", function(){ 6 | var pq = new jssim.MinPQ(); 7 | pq.clear(); 8 | pq.enqueue(100); 9 | pq.enqueue(10); 10 | expect(pq.min()).to.equal(10); 11 | pq.enqueue(1); 12 | expect(pq.size()).to.equal(3); 13 | expect(pq.isEmpty()).to.equal(false); 14 | expect(pq.delMin()).to.equal(1); 15 | expect(pq.delMin()).to.equal(10); 16 | expect(pq.delMin()).to.equal(100); 17 | expect(pq.isEmpty()).to.equal(true); 18 | }); 19 | 20 | 21 | }); -------------------------------------------------------------------------------- /test/near-beer-game-spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jssim = require('../src/jssim'); 3 | 4 | describe('Near Beer Game', function(){ 5 | it('should work as a simple supply chain', function(){ 6 | var supplier_id = 'supplier'; 7 | var customer_id = 'customer'; 8 | var agent_id = 'agent'; 9 | 10 | 11 | var Customer = function() { 12 | jssim.SimEvent.call(this, 1); 13 | this.id = customer_id; 14 | this.activities = []; 15 | this.cumulativeUnfulfilledOrders = 0; 16 | }; 17 | 18 | Customer.prototype = Object.create(jssim.SimEvent.prototype); 19 | 20 | Customer.prototype.update = function (delta) { 21 | var inbox = this.readInBox(); 22 | 23 | var arrived_goods = 0; 24 | for(var i=0; i < inbox.length; ++i) { 25 | var msg = inbox[i]; 26 | if(msg.type == 'newShipment') { 27 | arrived_goods += msg.quantity; 28 | } 29 | } 30 | 31 | if(arrived_goods > 0) { 32 | this.cumulativeUnfulfilledOrders -= arrived_goods; 33 | this.sendMsg(agent_id, { 34 | type: 'newGDN', 35 | quantity: arrived_goods 36 | }); 37 | } 38 | 39 | var order = this.makeOrder(); 40 | this.cumulativeUnfulfilledOrders += order; 41 | 42 | var record = {}; 43 | record.weeks = this.time; 44 | record.order = order; 45 | record.totalOrder = this.cumulativeUnfulfilledOrders; 46 | record.received = arrived_goods; 47 | 48 | this.activities.push(record); 49 | }; 50 | 51 | Customer.prototype.makeOrder = function () { 52 | var weeks = this.time; 53 | var order = 15; 54 | if (weeks <= 3) 55 | { 56 | order = 10; 57 | } 58 | this.sendMsg(agent_id, { 59 | quantity: order, 60 | type: 'newOrder' 61 | }); 62 | return order; 63 | }; 64 | 65 | 66 | 67 | var NearBeerAgent = function() { 68 | jssim.SimEvent.call(this, 1); 69 | this.id = agent_id; 70 | this.inventoryOnHand = 25; 71 | this.outgoingInventory = 0; 72 | this.backlog = 0; 73 | this.activities = []; 74 | }; 75 | 76 | NearBeerAgent.prototype = Object.create(jssim.SimEvent.prototype); 77 | NearBeerAgent.prototype.update = function (deltaTime) { 78 | 79 | 80 | var arrived_raw_materials = 0; 81 | var inbox = this.readInBox(); 82 | for(var i=0; i < inbox.length; ++i) { 83 | var msg = inbox[i]; 84 | if(msg.type == 'newShipment') { 85 | arrived_raw_materials += msg.quantity; 86 | } else if(msg.type == 'newOrder') { 87 | this.backlog += msg.quantity; 88 | } else if(msg.type == 'newGDN') { // goods delivery note from customer 89 | this.backlog -= msg.quantity; 90 | this.outgoingInventory -= msg.quantity; 91 | } 92 | } 93 | 94 | var beers = this.brewBeers(arrived_raw_materials); 95 | 96 | this.inventoryOnHand += beers; 97 | 98 | var shipment = this.makeShipment(); 99 | var order = this.makeOrder(); 100 | 101 | var record = {}; 102 | record.newRawMaterialOrder = order; 103 | record.backlog = this.backlog; 104 | record.beers = beers; 105 | record.inventoryOnHand = this.inventoryOnHand; 106 | record.weeks = this.time; 107 | record.shipment = shipment; 108 | this.activities.push(record); 109 | }; 110 | 111 | NearBeerAgent.prototype.makeShipment = function () { 112 | var shipment = Math.min(this.backlog - this.outgoingInventory, this.inventoryOnHand); 113 | if(shipment > 0) { 114 | this.sendMsg(customer_id, { 115 | type: 'newShipment', 116 | quantity: shipment 117 | }); 118 | this.inventoryOnHand -= shipment; 119 | this.outgoingInventory += shipment; 120 | 121 | } 122 | return shipment; 123 | }; 124 | 125 | NearBeerAgent.prototype.makeOrder = function () { 126 | var weeks = this.time; 127 | var value = 0; 128 | if (weeks <= 4) 129 | { 130 | value = 10; 131 | } 132 | else if ((weeks > 4) && (weeks <= 7)) 133 | { 134 | value = 25; 135 | } 136 | else 137 | { 138 | value = 15; 139 | } 140 | 141 | this.sendMsg(supplier_id, { 142 | quantity: value, 143 | type: 'newOrder' 144 | }); 145 | 146 | return value; 147 | }; 148 | 149 | NearBeerAgent.prototype.brewBeers = function (rawMaterials) { 150 | return Math.floor(rawMaterials * (0.8 + Math.random() * 0.1 - 0.05)); 151 | }; 152 | 153 | var Supplier = function () { 154 | jssim.SimEvent.call(this, 2); // higher rank than agent and customer so that it executes first 155 | this.id = supplier_id; 156 | this.activities = []; 157 | this.totalShipment = 0; 158 | this.inventoryOnHand = 0; 159 | }; 160 | 161 | Supplier.prototype = Object.create(jssim.SimEvent.prototype); 162 | 163 | Supplier.prototype.update = function (delta) { 164 | var messages = this.readInBox(); 165 | var order = 0; 166 | for(var i=0; i < messages.length; ++i) { 167 | var msg = messages[i]; 168 | if(msg.type == 'newOrder') { 169 | order += msg.quantity; 170 | } 171 | } 172 | 173 | var produced = this.produce(order); 174 | var shipment = this.makeShipment(order); 175 | this.totalShipment += shipment; 176 | 177 | var record = {}; 178 | record.produced = produced; 179 | record.inventoryOnHand = this.inventoryOnHand; 180 | record.shipment = shipment; 181 | record.totalShipment = this.totalShipment; 182 | record.weeks = this.time; 183 | this.activities.push(record); 184 | }; 185 | 186 | Supplier.prototype.produce = function(order) { 187 | var produced = Math.max(10, order); 188 | if(Math.random() < 0.5) { 189 | produced -= 5; 190 | } 191 | this.inventoryOnHand += produced; 192 | return produced; 193 | } 194 | 195 | Supplier.prototype.makeShipment = function (order) { 196 | var shipment = Math.max(0, this.inventoryOnHand - order) 197 | if(shipment > 0){ 198 | this.sendMsg(agent_id, { 199 | quantity: shipment, 200 | type: 'newShipment' 201 | }); 202 | this.inventoryOnHand -= shipment; 203 | } 204 | return shipment; 205 | }; 206 | 207 | var scheduler = new jssim.Scheduler(); 208 | 209 | function reset() { 210 | scheduler.reset(); 211 | var customer = new Customer(); 212 | var supplier = new Supplier(); 213 | var agent = new NearBeerAgent(); 214 | 215 | scheduler.scheduleRepeatingIn(customer, 1); 216 | scheduler.scheduleRepeatingIn(supplier, 1); 217 | scheduler.scheduleRepeatingIn(agent, 1); 218 | } 219 | 220 | reset(); 221 | while(scheduler.current_time < 150) { 222 | scheduler.update(); 223 | } 224 | }) ; 225 | }); -------------------------------------------------------------------------------- /test/pso-spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jssim = require('../src/jssim'); 3 | 4 | describe('Particle Swarm', function() { 5 | it('should behavior like PSO', function(){ 6 | 7 | var global_best_x = 0; 8 | var global_best_y = 0; 9 | var global_best_cost = 1000000; 10 | var C1 = 1; 11 | var C2 = 2; 12 | var particleCount = 400; 13 | 14 | var ParticleAgent = function(id, world) { 15 | jssim.SimEvent.call(this); 16 | this.grid = world; 17 | this.x = Math.floor(Math.random() * 128); 18 | this.y = Math.floor(Math.random() * 128); 19 | this.localBestX = this.x; 20 | this.localBestY = this.y; 21 | this.localBestCost = 1000000; 22 | this.vX = 0; 23 | this.vY = 0; 24 | this.id = id; 25 | this.cost = 1000000; 26 | }; 27 | 28 | ParticleAgent.prototype = Object.create(jssim.SimEvent.prototype); 29 | ParticleAgent.prototype.update = function (deltaTime) { 30 | this.updateVelocity(); 31 | this.updatePosition(); 32 | this.evaluateCost(); 33 | this.updateLocalBest(); 34 | this.updateGlobalBest(); 35 | }; 36 | 37 | ParticleAgent.prototype.evaluateCost = function() { 38 | this.cost = RosenBrock(this.x, this.y); 39 | }; 40 | 41 | ParticleAgent.prototype.updateGlobalBest = function() { 42 | if(this.localBestCost > this.cost) { 43 | this.localBestX = this.x; 44 | this.localBestY = this.y; 45 | this.lcoalBestCost = this.cost; 46 | } 47 | }; 48 | 49 | ParticleAgent.prototype.updateLocalBest = function() { 50 | if(global_best_cost > this.cost) { 51 | global_best_cost = this.cost; 52 | global_best_x = this.x; 53 | global_best_y = this.y; 54 | } 55 | }; 56 | 57 | ParticleAgent.prototype.updateVelocity = function() { 58 | var oldVX = this.vX; 59 | var oldVY = this.vY; 60 | 61 | var oldX = this.x; 62 | var oldY = this.y; 63 | 64 | var r1 = Math.random(); 65 | var r2 = Math.random(); 66 | var r3 = Math.random(); 67 | 68 | var w = 0.5 + r3 / 2; 69 | var newVX = w * oldVX + C1 * r1 * (this.localBestX - oldX) + C2 * r2 * (global_best_x - oldX); 70 | 71 | r1 = Math.random(); 72 | r2 = Math.random(); 73 | r3 = Math.random(); 74 | 75 | w = 0.5 + r3 / 2; 76 | var newVY = w * oldVY + C1 * r1 * (this.localBestY - oldY) + C2 * r2 * (global_best_y - oldY); 77 | 78 | this.vX = newVX; 79 | this.vY = newVY; 80 | }; 81 | 82 | ParticleAgent.prototype.updatePosition = function() { 83 | this.grid.setCell(this.x, this.y, 0); 84 | this.x = Math.min(127, this.vX + this.x); 85 | this.y = Math.min(127, this.vY + this.y); 86 | this.x = Math.max(0, Math.floor(this.x)); 87 | this.y = Math.max(0, Math.floor(this.y)); 88 | this.grid.setCell(this.x, this.y, 1); 89 | }; 90 | 91 | var scheduler = new jssim.Scheduler(); 92 | var grid = new jssim.Grid(128, 128); 93 | grid.cellWidth = 5; 94 | grid.cellHeight = 5; 95 | grid.showPotentialField = true; 96 | 97 | function RosenBrock(x, y) { 98 | x = (x - 64) * 5.0 / 128.0; 99 | y = (y - 64) * 5.0 / 128.0; 100 | var expr1 = (x*x - y); 101 | var expr2 = 1 - x; 102 | return 100 * expr1*expr1 + expr2*expr2; 103 | } 104 | 105 | function Rastrigin(x, y) { 106 | x = (x - 64) * 5.12 / 128.0; 107 | y = (y - 64) * 5.12 / 128.0; 108 | return 20 + x*x - 10*Math.cos(2*Math.PI*x) + y*y - 10*Math.cos(2*Math.PI*y); 109 | } 110 | 111 | function Griewangk(x, y) { 112 | x = (x - 64) * 600 / 128.0; 113 | y = (y - 64) * 600 / 128.0; 114 | return (1 + (x*x) / 4000 + (y*y) / 4000 - Math.cos(x) * Math.cos(y / Math.sqrt(2))); 115 | } 116 | 117 | function init() { 118 | scheduler.reset(); 119 | grid.reset(); 120 | 121 | 122 | for(var x = 0; x < 128; ++x) { 123 | for(var y=0; y < 128; ++y) { 124 | grid.setPotential(x, y, Rastrigin(x, y)); 125 | } 126 | } 127 | 128 | global_best_x = Math.floor(Math.random() * 128); 129 | global_best_y = Math.floor(Math.random() * 128); 130 | global_best_cost = RosenBrock(global_best_x, global_best_y); 131 | 132 | for(var i = 0; i < particleCount; ++i) { 133 | scheduler.scheduleRepeatingIn(new ParticleAgent(i, grid), 1); 134 | } 135 | } 136 | 137 | init(); 138 | while(scheduler.current_time < 5) { 139 | scheduler.update(); 140 | } 141 | }); 142 | }); -------------------------------------------------------------------------------- /test/queue-spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jssim = require('../src/jssim'); 3 | 4 | describe('Queue', function(){ 5 | it('should perform FIFO', function(){ 6 | var queue = new jssim.Queue(); 7 | queue.enqueue(1); 8 | queue.enqueue(2); 9 | queue.enqueue(3); 10 | expect(queue.size()).to.equal(3); 11 | expect(queue.isEmpty()).to.equal(false); 12 | expect(queue.dequeue()).to.equal(1); 13 | expect(queue.dequeue()).to.equal(2); 14 | expect(queue.dequeue()).to.equal(3); 15 | expect(queue.isEmpty()).to.equal(true); 16 | queue.enqueue(3); 17 | expect(queue.size()).to.equal(1); 18 | expect(queue.dequeue()).to.equal(3); 19 | expect(queue.isEmpty()).to.equal(true); 20 | }); 21 | }); -------------------------------------------------------------------------------- /test/render-spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jssim = require('../src/jssim'); 3 | 4 | describe('Render coverage test', function(){ 5 | it('should increase the coverage for render() api in the jssim', function(){ 6 | var Context = function(id) { 7 | this.id = id; 8 | }; 9 | 10 | Context.prototype.clearRect = function(x, y, width, height) { 11 | console.log('clearRect(' + x + ', ' + y + ', ' + width + ', ' + height + ')'); 12 | }; 13 | 14 | Context.prototype.fillRect = function(x, y, width, height) { 15 | console.log('fillRect(' + x + ', ' + y + ', ' + width + ', ' + height + ')'); 16 | }; 17 | 18 | Context.prototype.stroke = function () { 19 | console.log('stroke()'); 20 | }; 21 | 22 | Context.prototype.beginPath = function () { 23 | console.log('beginPath()'); 24 | }; 25 | 26 | Context.prototype.moveTo = function(x, y) { 27 | console.log('moveTo(' + x + ', ' + y + ')'); 28 | }; 29 | 30 | Context.prototype.lineTo = function(x, y) { 31 | console.log('lineTo(' + x + ', ' + y + ')'); 32 | }; 33 | 34 | Context.prototype.fillText = function(text) { 35 | console.log('fillText( ' + text + ')'); 36 | }; 37 | 38 | var Canvas = function(id) { 39 | this.id = id; 40 | this.context = new Context('2d'); 41 | }; 42 | 43 | Canvas.prototype.getContext = function(id) { 44 | return this.context; 45 | }; 46 | 47 | var grid = new jssim.Grid(64, 64); 48 | grid.setCell(10, 10, 1); 49 | grid.setObstable(10, 10, 1); 50 | grid.setTarget(5, 5, 1); 51 | grid.showTrails = true; 52 | grid.showPotential = true; 53 | grid.setPotential(10, 10, 10); 54 | grid.setPotential(20, 20, 20); 55 | var space = new jssim.Space2D(); 56 | 57 | space.drawLine(0, 0, 10, 10); 58 | 59 | var network = new jssim.Network(10); 60 | space.network = network; 61 | 62 | network.addEdge(new jssim.Edge(0, 2)); 63 | 64 | 65 | var agent = new jssim.SimEvent(); 66 | agent.id = 0; 67 | space.updateAgent(agent, 10, 10); 68 | 69 | agent = new jssim.SimEvent(); 70 | agent.id = 2; 71 | space.updateAgent(agent, 10, 10); 72 | 73 | var canvas = new Canvas(); 74 | space.render(canvas); 75 | 76 | agent = new jssim.SimEvent(); 77 | console.log(agent.guid()); 78 | 79 | grid.render(canvas); 80 | 81 | }) ; 82 | }); -------------------------------------------------------------------------------- /test/scheduler-spec.js: -------------------------------------------------------------------------------- 1 | expect = require('chai').expect; 2 | jssim = require('../src/jssim'); 3 | 4 | describe('Discrete Event Scheduler', function(){ 5 | it('should remove events with same time and rank and with min time and highest rank first', function(){ 6 | var scheduler = new jssim.Scheduler(); 7 | for(var time = 9; time >= 0; --time) { 8 | for(var rank = 1; rank <= 3; ++rank) { 9 | for(var id = 0; id < 10; ++id) { 10 | var evt = new jssim.SimEvent(rank); 11 | evt.id = time + ":" + rank + ":" + id; 12 | evt.update = function(deltaTime){ 13 | console.log("event " + this.id + " fired"); 14 | 15 | if(Math.random() < 0.1){ 16 | scheduler.scheduleOnceIn((function(parent_rank, parent_id){ 17 | var child_event = function(rank){ 18 | jssim.SimEvent.call(this, rank); 19 | }; 20 | child_event.prototype = Object.create(jssim.SimEvent.prototype); 21 | child_event.prototype.update = function(deltaTime) { 22 | console.log("event with rank " + this.rank + " fired at time " + this.time + " from event " + parent_id); 23 | }; 24 | return new child_event(parent_rank); 25 | })(this.rank, this.id), 2); 26 | } 27 | } 28 | scheduler.schedule(evt, time); 29 | } 30 | } 31 | } 32 | 33 | scheduler.scheduleRepeatingIn((function(_rank){ 34 | var child_event = function(rank){ 35 | jssim.SimEvent.call(this, rank); 36 | this.id = 100; 37 | }; 38 | child_event.prototype = Object.create(jssim.SimEvent.prototype); 39 | child_event.prototype.update = function(deltaTime) { 40 | console.log("repeated event with id = " + this.id + " and rank " + this.rank + " fired at time " + this.time); 41 | }; 42 | 43 | return new child_event(_rank); 44 | })(4), 2); 45 | 46 | scheduler.scheduleRepeatingAt(new jssim.SimEvent(), 1, 2); 47 | 48 | var prev_time = 0; 49 | var prev_rank = 5; 50 | while(scheduler.hasEvents()) { 51 | var fired = scheduler.update_mini(); 52 | expect(scheduler.current_time).not.to.below(prev_time); 53 | if(prev_time == scheduler.current_time) { 54 | expect(scheduler.current_rank).to.below(prev_rank); 55 | prev_rank = scheduler.current_rank; 56 | } else { 57 | prev_rank = 5; 58 | } 59 | 60 | prev_time = scheduler.current_time; 61 | 62 | if(scheduler.current_time >= 20) { 63 | break; 64 | } 65 | 66 | } 67 | 68 | 69 | }); 70 | }); -------------------------------------------------------------------------------- /test/school-yard-spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jssim = require('../src/jssim'); 3 | 4 | describe ('School Yard Simulation', function () { 5 | it ('should generate forces to pull buddies together and random forces to allow them to explore new connection', function() { 6 | var Student = function(id, yard, network) { 7 | jssim.SimEvent.call(this); 8 | this.id = id; 9 | this.yard = yard; 10 | this.network = network; 11 | this.MAX_FORCES = 3.0; 12 | this.forceToSchoolMultiplier = 0.01; 13 | this.randomMultiplier = 0.1; 14 | }; 15 | Student.prototype = Object.create(jssim.SimEvent.prototype); 16 | Student.prototype.update = function(deltaTime) { 17 | var students = this.yard.findAllAgents(); 18 | 19 | 20 | var me = this.yard.getLocation(this.id); 21 | 22 | var sumForces = new jssim.Vector2D(0, 0); 23 | var forceVector = new jssim.Vector2D(0, 0); 24 | var edges = this.network.adj(this.id); 25 | var len = edges.length; 26 | for (var buddy = 0; buddy < len; ++buddy) 27 | { 28 | var e = edges[buddy]; 29 | var buddiness = e.info; 30 | 31 | 32 | var him = this.yard.getLocation(e.other(this.id)); 33 | 34 | if (buddiness >= 0) 35 | { 36 | forceVector.set((him.x - me.x) * buddiness, (him.y - me.y) * buddiness); 37 | if (forceVector.length() > this.MAX_FORCES) 38 | { 39 | forceVector.resize(this.MAX_FORCES); 40 | } 41 | } 42 | else 43 | { 44 | forceVector.set((me.x - him.x) * buddiness, (me.y - him.y) * buddiness); 45 | if (forceVector.length() > this.MAX_FORCES) 46 | { 47 | forceVector.resize(0); 48 | } 49 | else if(forceVector.length() > 0) 50 | { 51 | forceVector.resize(this.MAX_FORCES - forceVector.length()); 52 | } 53 | } 54 | 55 | sumForces.addIn(forceVector); 56 | } 57 | 58 | sumForces.addIn( 59 | new jssim.Vector2D((this.yard.width * 0.5 - me.x) * this.forceToSchoolMultiplier, (this.yard.height * 0.5 - me.y) * this.forceToSchoolMultiplier) 60 | ); 61 | 62 | sumForces.addIn( 63 | new jssim.Vector2D(this.randomMultiplier * (Math.random() * 1.0 - 0.5), this.randomMultiplier * Math.random() * 1.0 - 0.5)); 64 | 65 | sumForces.addIn(me); 66 | 67 | me.x = sumForces.x; 68 | me.y = sumForces.y; 69 | 70 | console.log("Student " + this.id + " is at (" + me.x + ", " + me.y + ") at time " + this.time); 71 | }; 72 | 73 | var scheduler = new jssim.Scheduler(); 74 | var yard = new jssim.Space2D(); 75 | var network = new jssim.Network(30); 76 | yard.width = 50; 77 | yard.height = 50; 78 | yard.network = network; 79 | 80 | scheduler.reset(); 81 | yard.reset(); 82 | network.reset(); 83 | for(var i=0; i < 30; ++i) { 84 | var student = new Student(i, yard, network); 85 | yard.updateAgent(student, Math.random() * yard.width, Math.random() * yard.height); 86 | scheduler.scheduleRepeatingIn(student, 1); 87 | } 88 | 89 | var buddies = {}; 90 | for (var i = 0; i < 30; ++i) 91 | { 92 | var student = i; 93 | var studentB = i; 94 | do 95 | { 96 | studentB = Math.floor(Math.random() * 30); 97 | } while (student == studentB); 98 | var buddiness = Math.random(); 99 | if(!network.connected(student, studentB)){ 100 | network.addEdge(new jssim.Edge(student, studentB, buddiness)); 101 | } 102 | 103 | var studentB = i; 104 | do 105 | { 106 | studentB = Math.floor(Math.random() * 30); 107 | } while (student == studentB); 108 | 109 | buddiness = Math.random(); 110 | if(!network.connected(student, studentB)){ 111 | network.addEdge(new jssim.Edge(student, studentB, buddiness)); 112 | } 113 | } 114 | 115 | while (scheduler.current_time < 2) { 116 | scheduler.update(); 117 | } 118 | 119 | 120 | }); 121 | }); -------------------------------------------------------------------------------- /test/solar-spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jssim = require('../src/jssim'); 3 | 4 | describe('Ball simulation', function(){ 5 | it('should simulate balls collision and spring effect', function(){ 6 | 7 | var sun_x = 400; 8 | var sun_y = 300; 9 | var SUN = 0; 10 | var PLUTO = 9; // Furthest-out body 11 | // distance from sun in 10^5 km 12 | var DISTANCE =[0, 579, 1082, 1496, 2279, 7786, 14335, 28725, 44951, 58700]; 13 | 14 | // diameters in 10 km 15 | var DIAMETER = [139200.0, 487.9, 1210.4, 1275.6, 679.4, 14298.4, 12053.6, 5111.8, 4952.8, 239.0]; 16 | 17 | // period in days 18 | var PERIOD = [1 /* don't care :-) */, 88.0, 224.7, 365.2, 687.0, 4331, 10747, 30589, 59800, 90588]; 19 | 20 | var scale = 400 / DISTANCE[PLUTO]; 21 | 22 | // improve the animation effect 23 | DIAMETER[SUN] = 5000; 24 | for(var i=0; i < DISTANCE.length; ++i) { 25 | DISTANCE[i] = scale * DISTANCE[i]; 26 | PERIOD[i] /= 10; 27 | DIAMETER[i] = scale * DIAMETER[i] / 10.0; 28 | } 29 | 30 | 31 | 32 | 33 | var Body = function(id, velocity, distanceFromSun, space, scheduler) { 34 | jssim.SimEvent.call(this); 35 | this.velocity = velocity; 36 | this.distanceFromSun = distanceFromSun; 37 | this.scheduler = scheduler; 38 | this.space = space; 39 | this.id = id; 40 | }; 41 | 42 | Body.prototype = Object.create(jssim.SimEvent.prototype); 43 | 44 | Body.prototype.update = function (deltaTime) { 45 | if (this.distanceFromSun > 0) // the sun's at 0, and you can't divide by 0 46 | { 47 | var theta = ((this.velocity / this.distanceFromSun) * this.scheduler.current_time)%(2*Math.PI) ; 48 | this.space.updateAgent(this, 49 | sun_x + this.distanceFromSun*Math.cos(theta), 50 | sun_y + this.distanceFromSun*Math.sin(theta)); 51 | } 52 | }; 53 | 54 | Body.prototype.draw = function(context, pos){ 55 | if(this.id == SUN) { 56 | context.fillStyle = '#ff0000'; 57 | } else { 58 | context.fillStyle = '#666666'; 59 | } 60 | context.beginPath(); 61 | context.arc(pos.x, pos.y, DIAMETER[this.id], 0, 2 * Math.PI); 62 | context.fill(); 63 | 64 | context.fillStyle = '#ffffff'; 65 | }; 66 | 67 | 68 | 69 | var scheduler = new jssim.Scheduler(); 70 | 71 | var space = new jssim.Space2D(); 72 | 73 | 74 | function reset() { 75 | scheduler.reset(); 76 | space.reset(); 77 | 78 | for(var i=0; i<10;i++) 79 | { 80 | var b = new Body(i, (2*Math.PI*DISTANCE[i]) / PERIOD[i], DISTANCE[i], space, scheduler); 81 | space.updateAgent(b, sun_x + DISTANCE[i], sun_y); 82 | scheduler.scheduleRepeatingIn(b, 1); 83 | } 84 | } 85 | 86 | reset(); 87 | 88 | while (scheduler.current_time < 2) { 89 | scheduler.update(); 90 | } 91 | 92 | }) ; 93 | }); -------------------------------------------------------------------------------- /test/stack-spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jssim = require('../src/jssim'); 3 | 4 | describe('Stack', function(){ 5 | it('should push and pop correctly', function(){ 6 | var stack = new jssim.Stack(); 7 | 8 | stack.push(1); 9 | stack.push(2); 10 | stack.push(3); 11 | expect(stack.size()).to.equal(3); 12 | expect(stack.isEmpty()).to.equal(false); 13 | expect(stack.pop()).to.equal(3); 14 | expect(stack.pop()).to.equal(2); 15 | expect(stack.pop()).to.equal(1); 16 | expect(stack.isEmpty()).to.equal(true); 17 | }) ; 18 | }); -------------------------------------------------------------------------------- /test/virus-spec.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jssim = require('../src/jssim'); 3 | 4 | describe('Virus simulation', function(){ 5 | it('should simulate virus infectino and curing', function(){ 6 | var XMIN = 0; 7 | var XMAX = 800; 8 | var YMIN = 0; 9 | var YMAX = 600; 10 | 11 | var DIAMETER = 8; 12 | 13 | var HEALING_DISTANCE = 20; 14 | var HEALING_DISTANCE_SQUARED = HEALING_DISTANCE * HEALING_DISTANCE; 15 | var INFECTION_DISTANCE = 20; 16 | var INFECTION_DISTANCE_SQUARED = INFECTION_DISTANCE * INFECTION_DISTANCE; 17 | 18 | var NUM_HUMANS = 100; 19 | var NUM_GOODS = 4; 20 | var NUM_EVILS = 4; 21 | 22 | var Evil = function(id, loc, space) { 23 | jssim.SimEvent.call(this); 24 | this.id = id; 25 | this.agentLocation = loc; 26 | this.color = '#ff0000'; 27 | this.desiredLocation = null; 28 | this.suggestedLocation = null; 29 | this.steps = 0; 30 | this.space = space; 31 | space.updateAgent(this, loc.x, loc.y); 32 | this.type = 'Evil'; 33 | this.greedy = false; 34 | }; 35 | 36 | Evil.prototype = Object.create(jssim.SimEvent.prototype); 37 | 38 | Evil.prototype.update = function(deltaTime) { 39 | var mysteriousObjects = this.space.getNeighborsWithinDistance(this.agentLocation, 10.0 * INFECTION_DISTANCE); 40 | 41 | var distance2DesiredLocation = 1000000; 42 | for(var i = 0 ; i < mysteriousObjects.length ; i++ ) 43 | { 44 | if(mysteriousObjects[i] != this ) 45 | { 46 | if(mysteriousObjects[i].type != 'Human') continue; 47 | var ta = mysteriousObjects[i]; 48 | if(ta.isInfected()) continue; 49 | if(withinInfectionDistance(this.agentLocation, ta.agentLocation)) 50 | ta.setInfected( true ); 51 | else 52 | { 53 | if(this.greedy) 54 | { 55 | var tmpDist = distanceSquared(this.agentLocation, ta.agentLocation); 56 | if(tmpDist < distance2DesiredLocation ) 57 | { 58 | this.desiredLocation = ta.agentLocation; 59 | distance2DesiredLocation = tmpDist; 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | 67 | this.steps--; 68 | if( this.desiredLocation == null || !this.greedy ) 69 | { 70 | if(this.steps <= 0 ) 71 | { 72 | this.suggestedLocation = new jssim.Vector2D((Math.random()-0.5)*((XMAX-XMIN)/5-DIAMETER) + this.agentLocation.x, 73 | (Math.random()-0.5)*((YMAX-YMIN)/5-DIAMETER) + this.agentLocation.y); 74 | this.steps = 100; 75 | } 76 | this.desiredLocation = this.suggestedLocation; 77 | } 78 | 79 | var dx = this.desiredLocation.x - this.agentLocation.x; 80 | var dy = this.desiredLocation.y - this.agentLocation.y; 81 | 82 | var temp = 0.5 * Math.sqrt(dx*dx+dy*dy); 83 | if( temp < 1 ) 84 | { 85 | this.steps = 0; 86 | } 87 | else 88 | { 89 | dx /= temp; 90 | dy /= temp; 91 | } 92 | 93 | 94 | if( !acceptablePosition(this, new jssim.Vector2D(this.agentLocation.x + dx, this.agentLocation.y + dy), this.space) ) 95 | { 96 | this.steps = 0; 97 | } 98 | else 99 | { 100 | this.agentLocation = new jssim.Vector2D(this.agentLocation.x + dx, this.agentLocation.y + dy); 101 | space.updateAgent(this, this.agentLocation.x, this.agentLocation.y); 102 | } 103 | }; 104 | 105 | var Good = function(id, loc, space) { 106 | jssim.SimEvent.call(this); 107 | this.id = id; 108 | this.agentLocation = loc; 109 | this.color = '#00ff00'; 110 | this.desiredLocation = null; 111 | this.suggestedLocation = null; 112 | this.steps = 0; 113 | this.space = space; 114 | space.updateAgent(this, loc.x, loc.y); 115 | this.type = 'Good'; 116 | this.greedy = true; 117 | }; 118 | 119 | Good.prototype = Object.create(jssim.SimEvent.prototype); 120 | 121 | Good.prototype.update = function(deltaTime) { 122 | var mysteriousObjects = this.space.getNeighborsWithinDistance(this.agentLocation, 10.0 * INFECTION_DISTANCE); 123 | 124 | var distance2DesiredLocation = 1000000; 125 | for(var i = 0 ; i < mysteriousObjects.length ; i++ ) 126 | { 127 | if(mysteriousObjects[i] != this ) 128 | { 129 | if(mysteriousObjects[i].type != 'Human') continue; 130 | var ta = mysteriousObjects[i]; 131 | if(!ta.isInfected()) continue; 132 | if(withinHealingDistance(this.agentLocation, ta.agentLocation)) 133 | ta.setInfected(false); 134 | else 135 | { 136 | if(this.greedy) 137 | { 138 | var tmpDist = distanceSquared(this.agentLocation, ta.agentLocation); 139 | if(tmpDist < distance2DesiredLocation ) 140 | { 141 | this.desiredLocation = ta.agentLocation; 142 | distance2DesiredLocation = tmpDist; 143 | } 144 | } 145 | } 146 | } 147 | } 148 | 149 | 150 | this.steps--; 151 | if( this.desiredLocation == null || !this.greedy ) 152 | { 153 | if(this.steps <= 0 ) 154 | { 155 | this.suggestedLocation = new jssim.Vector2D((Math.random()-0.5)*((XMAX-XMIN)/5-DIAMETER) + this.agentLocation.x, 156 | (Math.random()-0.5)*((YMAX-YMIN)/5-DIAMETER) + this.agentLocation.y); 157 | this.steps = 100; 158 | } 159 | this.desiredLocation = this.suggestedLocation; 160 | } 161 | 162 | var dx = this.desiredLocation.x - this.agentLocation.x; 163 | var dy = this.desiredLocation.y - this.agentLocation.y; 164 | 165 | var temp = 0.5 * Math.sqrt(dx*dx+dy*dy); 166 | if( temp < 1 ) 167 | { 168 | this.steps = 0; 169 | } 170 | else 171 | { 172 | dx /= temp; 173 | dy /= temp; 174 | } 175 | 176 | 177 | if( !acceptablePosition(this, new jssim.Vector2D(this.agentLocation.x + dx, this.agentLocation.y + dy), this.space)) 178 | { 179 | this.steps = 0; 180 | } 181 | else 182 | { 183 | this.agentLocation = new jssim.Vector2D(this.agentLocation.x + dx, this.agentLocation.y + dy); 184 | space.updateAgent(this, this.agentLocation.x, this.agentLocation.y); 185 | } 186 | }; 187 | 188 | var Human = function(id, loc, space) { 189 | jssim.SimEvent.call(this); 190 | this.id = id; 191 | this.agentLocation = loc; 192 | this.color = '#ff8800'; 193 | this.desiredLocation = null; 194 | this.suggestedLocation = null; 195 | this.steps = 0; 196 | this.space = space; 197 | space.updateAgent(this, loc.x, loc.y); 198 | this.type = 'Human'; 199 | this.infected = false; 200 | }; 201 | 202 | Human.prototype = Object.create(jssim.SimEvent.prototype); 203 | 204 | Human.prototype.setInfected = function(infected) { 205 | this.infected = infected; 206 | if(infected){ 207 | this.color = '#5533ff'; 208 | } else { 209 | this.color = '#ff8800'; 210 | } 211 | }; 212 | 213 | Human.prototype.isInfected = function() { 214 | return this.infected; 215 | }; 216 | 217 | Human.prototype.update = function(deltaTime) { 218 | this.steps--; 219 | if( this.desiredLocation == null || this.steps <= 0 ) 220 | { 221 | this.desiredLocation = new jssim.Vector2D((Math.random()-0.5)*((XMAX-XMIN)/5-DIAMETER) + this.agentLocation.x, 222 | (Math.random()-0.5)*((YMAX-YMIN)/5-DIAMETER) + this.agentLocation.y); 223 | this.steps = 50 + Math.floor(Math.random() * 50); 224 | } 225 | 226 | var dx = this.desiredLocation.x - this.agentLocation.x; 227 | var dy = this.desiredLocation.y - this.agentLocation.y; 228 | 229 | 230 | var temp = Math.sqrt(dx*dx+dy*dy); 231 | if( temp < 1 ) 232 | { 233 | this.steps = 0; 234 | } 235 | else 236 | { 237 | dx /= temp; 238 | dy /= temp; 239 | } 240 | 241 | 242 | if( ! acceptablePosition(this, new jssim.Vector2D(this.agentLocation.x + dx, this.agentLocation.y + dy ), this.space) ) 243 | { 244 | steps = 0; 245 | } 246 | else 247 | { 248 | this.agentLocation = new jssim.Vector2D(this.agentLocation.x + dx, this.agentLocation.y + dy); 249 | this.space.updateAgent(this, this.agentLocation.x, this.agentLocation.y); 250 | } 251 | }; 252 | 253 | 254 | 255 | function distanceSquared(loc1, loc2) 256 | { 257 | return( (loc1.x-loc2.x)*(loc1.x-loc2.x)+(loc1.y-loc2.y)*(loc1.y-loc2.y) ); 258 | } 259 | 260 | function conflict(a, b) 261 | { 262 | if( ( ( a.x > b.x && a.x < b.x+DIAMETER ) || 263 | ( a.x+DIAMETER > b.x && a.x+DIAMETER < b.x+DIAMETER ) ) && 264 | ( ( a.y > b.y && a.y < b.y+DIAMETER ) || 265 | ( a.y+DIAMETER > b.y && a.y+DIAMETER < b.y+DIAMETER ) ) ) 266 | { 267 | return true; 268 | } 269 | return false; 270 | } 271 | 272 | function withinInfectionDistance(a, b) 273 | { 274 | return ( (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y) <= INFECTION_DISTANCE_SQUARED ); 275 | } 276 | 277 | function withinHealingDistance(a, b ) 278 | { 279 | return ( (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y) <= HEALING_DISTANCE_SQUARED ); 280 | } 281 | 282 | function acceptablePosition(agent, location, space) 283 | { 284 | if( location.x < DIAMETER/2 || location.x > (XMAX-XMIN)-DIAMETER/2 || 285 | location.y < DIAMETER/2 || location.y > (YMAX-YMIN)-DIAMETER/2 ) 286 | return false; 287 | var mysteriousObjects = space.getNeighborsWithinDistance( location, 2*DIAMETER ); 288 | for(var i = 0 ; i < mysteriousObjects.length ; i++ ) 289 | { 290 | if(mysteriousObjects[i] != agent) 291 | { 292 | var ta = mysteriousObjects[i]; 293 | if(conflict(location, space.getLocation(ta.id))) return false; 294 | } 295 | } 296 | return true; 297 | } 298 | 299 | 300 | var scheduler = new jssim.Scheduler(); 301 | 302 | var space = new jssim.Space2D(); 303 | 304 | 305 | function reset() { 306 | scheduler.reset(); 307 | space.reset(); 308 | 309 | for(var x=0;x