-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsimulator.js
237 lines (221 loc) · 7.66 KB
/
simulator.js
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/// <summary> Controls the nanomuncher core simulation. </summary>
var Simulator = function(board){
this.munchers = [];
this.board = board;
this.time = 0;
this.nodesMunched = 0;
/// <summary> Drop a muncher at the given node. </summary>
this.dropMuncher = function(player, node, program){
// Drop when not occupied.
var occupied = this.munchers.some(function(e){
return node === e.node;
}, this);
if(!occupied && !node.munched()){
var muncher = new Muncher(player, node, this.time, program);
this.munchers.push(muncher);
return muncher;
}else{ return {dead: true}; }
}.bind(this)
/// <summary> Internal method to resolve muncher conflicts. </summary>
var resolveConflicts = function(){
// Hash munchers based on current node.
var conflictMap = {}
this.munchers.forEach(function(muncher){
code = muncher.node.toS();
if(conflictMap[code] === undefined){
conflictMap[code] = [];
}
conflictMap[code].push(muncher);
}, this)
// Resolve conflicts using precedence rules.
var resolvePrecedence = ["R", "D", "L", "U"];
for(p in conflictMap){
if(conflictMap.hasOwnProperty(p)){
var nodeConflict = conflictMap[p];
if(nodeConflict.length > 1){
// Reverse sort by precedence.
nodeConflict.sort(function(a, b){
// Check newly dropped.
if(a.startTime === this.time){
}
resolvePrecedence.indexOf(a) < resolvePrecedence.indexOf(b);
});
// Keep highest precedence.
nodeConflict.shift();
// Remove all others.
nodeConflict.forEach(function(e){
e.dead = true;
this.munchers.splice(this.munchers.indexOf(e), 1);
}, this);
}
}
}
}.bind(this)
/// <summary> Internal method to have munchers eat at their node. </sumamry>
var munch = function(){
this.munchers.forEach(function(muncher){
muncher.node.munch(muncher.player);
this.nodesMunched++;
}, this);
}.bind(this)
/// <summary> Internal function to move munchers. </summary>
var move = function(){
// Move and filter black holes.
this.munchers = this.munchers.filter(function(muncher){
// Move the muncher to the next step in the program.
var blackHole = true;
for(i = 0; i < 4; ++i){
// Get instruction and rotate program.
var instruction = muncher.programState.shift();
muncher.programState.push(instruction);
var nextNode = muncher.node[instruction];
if((nextNode !== undefined) && !nextNode.munched()){
muncher.node = nextNode;
blackHole = false;
break;
}
}
if(blackHole){
muncher.dead = true;
}
return !blackHole;
});
}.bind(this)
/// <summary> Advance the simulation by one time step. </summary>
this.stepTime = function(){
// 1. Munchers are dropped.
// 2. Munchers resolve conflicts.
// 3. Munchers munch.
// 4. Munchers move.
// 5. Back to 1.
// This function assumes that all muchers have been
// dropped. It runs steps 2-4.
resolveConflicts();
munch();
move();
++this.time;
}.bind(this);
/// <summary> Return whether all nodes have been munched. </summary>
this.allNodesMunched = function(){
return this.nodesMunched === this.board.numNodes;
},
/// <summary> Return the number of munchers remaining
// on the board. </summary>
this.munchersRemaining = function(){
return this.munchers.length;
}
}
bindAllFunctions(Simulator);
/// <summary> Muncher logic object used during simulation. </summary>
var Muncher = function(player, startNode, startTime, program){
// The time that the muncher was dropped.
this.startTime = startTime;
// The nanomuncher location.
this.node = startNode;
// The nanomuncher program.
this.program = (typeof(program) !== 'undefined') ? program
: this.randomProgram();
// The executing program.
this.programState = this.program;
// The id of the owning player.
this.player = player;
this.dead = false;
}
// Generate a random program.
Muncher.randomProgram = function(){
return Array.shuffle(["L","U","R","D"]);
}
bindAllFunctions(Muncher);
/// <summary> A board is a set of nodes and edges. </summary>
/// <remarks>
/// <para> Each node on the board has a set of links keyed by the allowed
/// Nanomuncher program instructions. These properties may be used to test
/// and traverse from a node to one of its neighbors.
/// </para>
/// </remarks>
Board = function(xSize, ySize, numNodes, edgeProb){
this.xSize = typeof(xSize) !== 'undefined' ? xSize : 30;
this.ySize = typeof(ySize) !== 'undefined' ? ySize : 20;
this.numNodes = typeof(numNodes) !== 'undefined' ? numNodes : 700;
// if there could be an edge between i and j, there
// is an edgeprob chance that it will be there
this.edgeProb = typeof(edgeProb) !== 'undefined' ? edgeProb : 0.7;
// Create all possible nodes.
this.nodes = []
for(var i = 0; i < xSize; i++){
for(var j = 0; j < ySize; j++){
this.nodes.push(new Node(i,j));
}
}
// Pick a random subset of size numNodes.
Array.shuffle(this.nodes);
this.nodes.splice(numNodes, this.nodes.length - numNodes);
// Create random edges based on edgeProb and create links.
this.edges = [];
for(var i = 0; i < this.nodes.length; i++){
for(var j = i; j < this.nodes.length; j++){
if(this.nodes[i].isNeighbor(this.nodes[j])){
if(Math.random() <= edgeProb){
this.edges.push([i,j]);
if(this.nodes[i].x === this.nodes[j].x){
// Higher y value is above
if(this.nodes[i].y === this.nodes[j].y + 1){
this.nodes[j]["U"] = this.nodes[i]
this.nodes[i]["D"] = this.nodes[j]
}else{
this.nodes[j]["D"] = this.nodes[i]
this.nodes[i]["U"] = this.nodes[j]
}
}else if(this.nodes[i].y === this.nodes[j].y){
// Higher x value is to the left
if(this.nodes[i].x === this.nodes[j].x + 1){
this.nodes[i]["R"] = this.nodes[j];
this.nodes[j]["L"] = this.nodes[i];
}else{
this.nodes[i]["L"] = this.nodes[j];
this.nodes[j]["R"] = this.nodes[i];
}
}
}
}
}
}
}
/// <summary> A node is 2d point location. </summary>
/// <remarks>
/// <para> Nodes contain toxic waste that must be munched by a
/// player as the objective of the game.
/// </para>
/// </remarks>
var Node = function(x_, y_){
this.x = x_;
this.y = y_;
this.munchedBy = null;
// Test if another node is the same.
this.isSame = function(nodeB){
return (this.x === nodeB.x) && (this.y === nodeB.y)
};
// Calculate the distance between two nodes.
this.distance = function(b){
return Math.sqrt(Math.pow((this.x - b.x),2) + Math.pow((this.y-b.y),2));
};
// Check if the node is a neighbor.
this.isNeighbor = function(nodeB){
return (this.x === nodeB.x && Math.abs(this.y - nodeB.y) === 1) ||
(this.y === nodeB.y && Math.abs(this.x - nodeB.x) === 1);
};
// Consume this node.
this.munch = function(munchedBy){
this.munchedBy = munchedBy;
if(this.canvasElement){
this.canvasElement.attr('fill', munchedBy.colorScheme[1]);
}
};
// Is munched?
this.munched = function(){ return this.munchedBy !== null; };
}
// Inherit mathematical helpers from Point.
Node.prototype.add = Point.prototype.add;
Node.prototype.sub = Point.prototype.sub;
Node.prototype.toS = Point.prototype.toS;
bindAllFunctions(Node)