Neale Pickett
·
2024-11-05
jstanks.js
1"use strict";
2/*
3 * jstanks: A forf/tanks implementation in javascript, based on the C version.
4 * Copyright (C) 2014 Alyssa Milburn
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/*
21 * Disclaimer: I warned you all that I don't know javascript.
22 *
23 * TODO:
24 * - memory functions
25 * - peer at the arithmetic FIXMEs
26 * - overflow/underflow checks
27 * - do the substacks properly, not as a parse-time hack
28 * - type checking
29 * (one of those two should stop '{ dup 1 exch if } dup 1 exch if' from working)
30 *
31 * - tests
32 * - catch/show exceptions
33 * - save/load state from cookie
34 * - stack visualisation
35 * - display live desired/current speed, turret angle, etc
36 * - show live sensor state in the Sensors box too?
37 * - scoreboard
38 * - apply simultaneous fire fixes and/or other upstream changes
39 */
40
41var TAU = 2 * Math.PI;
42
43var mod = function(a, b) { return a % b; };
44var sq = function(a) { return a * a; };
45
46var rad2deg = function(r) { return Math.floor(360*(r)/TAU); };
47var deg2rad = function(r) { return (r*TAU)/360; };
48
49/* Some in-game constants */
50var TANK_MAX_SENSORS = 10;
51var TANK_RADIUS = 7.5;
52var TANK_SENSOR_RANGE = 100;
53var TANK_CANNON_RECHARGE = 20; /* Turns to recharge cannon */
54var TANK_CANNON_RANGE = (TANK_SENSOR_RANGE / 2);
55var TANK_MAX_ACCEL = 35;
56var TANK_MAX_TURRET_ROT = (TAU/8);
57var TANK_TOP_SPEED = 7;
58var TANK_FRICTION = 0.75;
59
60/* (tank radius + tank radius)^2 */
61var TANK_COLLISION_ADJ2 = ((TANK_RADIUS + TANK_RADIUS) * (TANK_RADIUS + TANK_RADIUS));
62
63/* (Sensor range + tank radius)^2
64 * If the distance^2 to the center of a tank <= TANK_SENSOR_ADJ2,
65 * that tank is within sensor range. */
66var TANK_SENSOR_ADJ2 = ((TANK_SENSOR_RANGE + TANK_RADIUS) * (TANK_SENSOR_RANGE + TANK_RADIUS));
67
68var TANK_CANNON_ADJ2 = ((TANK_CANNON_RANGE + TANK_RADIUS) * (TANK_CANNON_RANGE + TANK_RADIUS));
69
70// initial game grid spacing
71var SPACING = 150;
72
73var MEMORY_SIZE = 10;
74
75var Forf = function() {
76 this.mem = new Object();
77 this.builtins = new Object();
78
79 this.builtins["debug!"] = function(myforf) { document.getElementById('debug').innerHTML = myforf.popData(); };
80 var unfunc = function(func) {
81 return function(myforf) {
82 var a = myforf.popData();
83 myforf.datastack.push(~~func(a)); // truncate, FIXME
84 };
85 };
86 var binfunc = function(func) {
87 return function(myforf) {
88 var a = myforf.popData();
89 var b = myforf.popData();
90 myforf.datastack.push(~~func(b,a)); // truncate?, FIXME
91 };
92 };
93 this.builtins["~"] = unfunc(function(a) { return ~a; });
94 this.builtins["!"] = unfunc(function(a) { return !a; });
95 this.builtins["+"] = binfunc(function(a, b) { return a+b; });
96 this.builtins["-"] = binfunc(function(a, b) { return a-b; });
97 this.builtins["/"] = binfunc(function(a, b) {
98 if (b === 0) { throw "division by zero"; }
99 return a/b;
100 });
101 this.builtins["%"] = binfunc(function(a, b) {
102 if (b === 0) { throw "division by zero"; }
103 return mod(a,b);
104 });
105 this.builtins["*"] = binfunc(function(a, b) { return a*b; });
106 this.builtins["&"] = binfunc(function(a, b) { return a&b; });
107 this.builtins["|"] = binfunc(function(a, b) { return a|b; });
108 this.builtins["^"] = binfunc(function(a, b) { return a^b; });
109 this.builtins["<<"] = binfunc(function(a, b) { return a<<b; });
110 this.builtins[">>"] = binfunc(function(a, b) { return a>>b; });
111 this.builtins[">"] = binfunc(function(a, b) { return a>b; });
112 this.builtins[">="] = binfunc(function(a, b) { return a>=b; });
113 this.builtins["<"] = binfunc(function(a, b) { return a<b; });
114 this.builtins["<="] = binfunc(function(a, b) { return a<=b; });
115 this.builtins["="] = binfunc(function(a, b) { return a===b; });
116 this.builtins["<>"] = binfunc(function(a, b) { return a!==b; });
117 this.builtins["abs"] = unfunc(function(a) { return Math.abs(a); });
118 // FIXME: the three following functions can only manipulate numbers in cforf
119 this.builtins["dup"] = function(myforf) {
120 var val = myforf.popData();
121 myforf.datastack.push(val);
122 myforf.datastack.push(val);
123 };
124 this.builtins["pop"] = function(myforf) {
125 myforf.popData();
126 };
127 this.builtins["exch"] = function(myforf) {
128 var a = myforf.popData();
129 var b = myforf.popData();
130 myforf.datastack.push(a);
131 myforf.datastack.push(b);
132 };
133 this.builtins["if"] = function(myforf) {
134 var ifclause = myforf.popData();
135 var cond = myforf.popData();
136 if (cond) {
137 // TODO: make sure ifclause is a list
138 for (var i = 0; i < ifclause.length; i++) {
139 myforf.cmdstack.push(ifclause[i]);
140 }
141 }
142 };
143 this.builtins["ifelse"] = function(myforf) {
144 var elseclause = myforf.popData();
145 var ifclause = myforf.popData();
146 var cond = myforf.popData();
147 if (!cond) {
148 ifclause = elseclause;
149 }
150 // TODO: make sure ifclause is a list
151 for (var i = 0; i < ifclause.length; i++) {
152 myforf.cmdstack.push(ifclause[i]);
153 }
154 };
155 this.builtins["mset"] = function(myforf) {
156 var pos = myforf.popData();
157 var a = myforf.popData();
158 if (pos < 0 || pos >= MEMORY_SIZE) {
159 throw "invalid memory location";
160 }
161 myforf.mem[pos] = a;
162 };
163 this.builtins["mget"] = function(myforf) {
164 var pos = myforf.popData();
165 if (pos < 0 || pos >= MEMORY_SIZE) {
166 throw "invalid memory location";
167 }
168 myforf.datastack.push(myforf.mem[pos]);
169 };
170};
171
172Forf.prototype.popData = function() {
173 if (this.datastack.length === 0) {
174 throw "tried to pop from empty stack";
175 }
176 return this.datastack.pop();
177};
178
179Forf.prototype.init = function(code) {
180 this.code = code;
181};
182
183Forf.prototype.parse = function() {
184 this.cmdstack = [];
185
186 // 'parse' the input
187 this.code = this.code.replace(/\([^)]*\)/g, "");
188 var splitCode = this.code.split(/([{}])/).join(" ");
189 var tokens = splitCode.split(/\s+/).filter(Boolean); // filter to deal with newlines etc
190 // FIXME: this is a hack right now because ugh stacks
191 var parseTokensAt = function(i, stack) {
192 var val = tokens[i];
193 if (val === "{") {
194 var dststack = [];
195 i = i + 1;
196 while (i < tokens.length) {
197 if (tokens[i] === "}") {
198 break;
199 }
200 i = parseTokensAt(i, dststack);
201 }
202 stack.push(dststack.reverse());
203 } else {
204 // replace numbers with actual numbers
205 var n = parseInt(val);
206 if (String(n) === val) {
207 stack.push(n);
208 } else {
209 stack.push(val);
210 }
211 }
212 return i + 1;
213 };
214 var i = 0;
215 while (i < tokens.length) {
216 i = parseTokensAt(i, this.cmdstack);
217 }
218
219 // The first thing we read should be the first thing we do.
220 this.cmdstack = this.cmdstack.reverse();
221};
222
223Forf.prototype.run = function() {
224 this.datastack = [];
225
226 var running = true;
227 while (running && this.cmdstack.length) {
228 var val = this.cmdstack.pop();
229 if (typeof(val) == "string") {
230 var func = this.builtins[val];
231 if (val in this.builtins) {
232 func(this);
233 } else {
234 throw "no such function '" + val + "'";
235 }
236 } else {
237 this.datastack.push(val);
238 }
239 }
240};
241
242var gameSize = [0, 0];
243
244var ForfTank = function() {
245 // http://www.paulirish.com/2009/random-hex-color-code-snippets/
246 this.color = '#'+(4473924+Math.floor(Math.random()*12303291)).toString(16);
247
248 this.sensors = [];
249 this.position = [0, 0];
250 this.angle = 0;
251 this.speed = new Object;
252 this.speed.desired = [0, 0];
253 this.speed.current = [0, 0];
254 this.turret = new Object;
255 this.turret.current = 0;
256 this.turret.desired = 0;
257 this.turret.firing = 0;
258 this.turret.recharge = 0;
259 this.led = 0;
260 this.killer = null;
261 this.cause_death = "";
262
263 this.builtins["fire-ready?"] = function(myforf) {
264 myforf.datastack.push(myforf.fireReady());
265 };
266 this.builtins["fire!"] = function(myforf) {
267 myforf.fire();
268 };
269 this.builtins["set-speed!"] = function(myforf) {
270 var right = myforf.popData();
271 var left = myforf.popData();
272 myforf.setSpeed(left, right);
273 };
274 this.builtins["set-turret!"] = function(myforf) {
275 var angle = myforf.popData();
276 myforf.setTurret(deg2rad(angle));
277 };
278 this.builtins["get-turret"] = function(myforf) {
279 var angle = myforf.getTurret();
280 myforf.datastack.push(rad2deg(angle));
281 };
282 this.builtins["sensor?"] = function(myforf) {
283 var sensor_num = myforf.popData();
284 myforf.datastack.push(myforf.getSensor(sensor_num));
285 };
286 this.builtins["set-led!"] = function(myforf) {
287 var active = myforf.popData();
288 myforf.setLed(active);
289 };
290 this.builtins["random"] = function(myforf) {
291 var max = myforf.popData();
292 if (max < 1) {
293 myforf.datastack.push(0);
294 return;
295 }
296 myforf.datastack.push(Math.floor(Math.random() * max));
297 };
298};
299ForfTank.prototype = new Forf();
300ForfTank.prototype.constructor = ForfTank;
301
302ForfTank.prototype.addSensor = function(range, angle, width, turret) {
303 var sensor = new Object();
304 sensor.range = range;
305 sensor.angle = deg2rad(angle);
306 sensor.width = deg2rad(width);
307 sensor.turret = turret;
308 this.sensors.push(sensor);
309};
310
311ForfTank.prototype.fireReady = function() {
312 return !this.turret.recharge;
313};
314
315ForfTank.prototype.fire = function() {
316 this.turret.firing = this.fireReady();
317};
318
319ForfTank.prototype.setSpeed = function(left, right) {
320 this.speed.desired[0] = Math.min(Math.max(left, -100), 100);
321 this.speed.desired[1] = Math.min(Math.max(right, -100), 100);
322};
323
324ForfTank.prototype.getTurret = function() {
325 return this.turret.current;
326};
327
328ForfTank.prototype.setTurret = function(angle) {
329 this.turret.desired = mod(angle, TAU);
330};
331
332ForfTank.prototype.getSensor = function(sensor_num) {
333 if ((sensor_num < 0) || (sensor_num >= this.sensors.length)) {
334 return 0;
335 } else {
336 return this.sensors[sensor_num].triggered;
337 }
338};
339
340ForfTank.prototype.setLed = function(active) {
341 this.led = active;
342};
343
344ForfTank.prototype.move = function() {
345 var dir = 1;
346 var movement;
347 var angle;
348
349 /* Rotate the turret */
350 var rot_angle; /* Quickest way there */
351
352 /* Constrain rot_angle to between -PI and PI */
353 rot_angle = this.turret.desired - this.turret.current;
354 while (rot_angle < 0) {
355 rot_angle += TAU;
356 }
357 rot_angle = mod(Math.PI + rot_angle, TAU) - Math.PI;
358
359 rot_angle = Math.min(TANK_MAX_TURRET_ROT, rot_angle);
360 rot_angle = Math.max(-TANK_MAX_TURRET_ROT, rot_angle);
361 this.turret.current = mod(this.turret.current + rot_angle, TAU);
362
363 /* Fakey acceleration */
364 for (var i = 0; i < 2; i++) {
365 if (this.speed.current[i] === this.speed.desired[i]) {
366 /* Do nothing */
367 } else if (this.speed.current[i] < this.speed.desired[i]) {
368 this.speed.current[i] = Math.min(this.speed.current[i] + TANK_MAX_ACCEL,
369 this.speed.desired[i]);
370 } else {
371 this.speed.current[i] = Math.max(this.speed.current[i] - TANK_MAX_ACCEL,
372 this.speed.desired[i]);
373 }
374 }
375
376 /* The simple case */
377 if (this.speed.current[0] === this.speed.current[1]) {
378 movement = this.speed.current[0] * (TANK_TOP_SPEED / 100.0);
379 angle = 0;
380 } else {
381 /* pflarr's original comment:
382 *
383 * The tank drives around in a circle of radius r, which is some
384 * offset on a line perpendicular to the tank. The distance it
385 * travels around the circle varies with the speed of each tread,
386 * and is such that each side of the tank moves an equal angle
387 * around the circle.
388 *
389 * Sounds good to me. pflarr's calculations here are fantastico,
390 * there's nothing whatsoever to change. */
391
392 /* The first thing Paul's code does is find "friction", which seems
393 to be a penalty for having the treads go in opposite directions.
394 This probably plays hell with precisely-planned tanks, which I
395 find very ha ha. */
396 var friction = TANK_FRICTION * (Math.abs(this.speed.current[0] - this.speed.current[1]) / 200);
397 var v = [0, 0];
398 v[0] = this.speed.current[0] * (1 - friction) * (TANK_TOP_SPEED / 100.0);
399 v[1] = this.speed.current[1] * (1 - friction) * (TANK_TOP_SPEED / 100.0);
400
401 var Si;
402 var So;
403 /* Outside and inside speeds */
404 if (Math.abs(v[0]) > Math.abs(v[1])) {
405 Si = v[1];
406 So = v[0];
407 dir = 1;
408 } else {
409 Si = v[0];
410 So = v[1];
411 dir = -1;
412 }
413
414 /* Radius of circle to outside tread (use similar triangles) */
415 var r = So * (TANK_RADIUS * 2) / (So - Si);
416
417 /* pflarr:
418
419 The fraction of the circle traveled is equal to the speed
420 of the outer tread over the circumference of the circle:
421 Ft = So/(tau*r)
422 The angle traveled is:
423 theta = Ft * tau
424 This reduces to a simple
425 theta = So/r
426 We multiply it by dir to adjust for the direction of rotation
427 */
428 var theta = So/r * dir;
429
430 movement = r * Math.tan(theta);
431 angle = theta;
432 }
433
434 /* Now move the tank */
435 this.angle = mod(this.angle + angle + TAU, TAU);
436 var m = [0, 0];
437
438 m[0] = Math.cos(this.angle) * movement * dir;
439 m[1] = Math.sin(this.angle) * movement * dir;
440
441 for (var i = 0; i < 2; i++) {
442 this.position[i] = mod(this.position[i] + m[i] + gameSize[i], gameSize[i]);
443 }
444};
445
446var ftanks = [];
447var tanks = [];
448var interval = null;
449
450var initTanks = function(tanks) {
451 var ntanks = tanks.length;
452
453 // Calculate the size of the game board.
454 var x = 1;
455 while (x * x < ntanks) {
456 x = x + 1;
457 }
458 var y = Math.floor(ntanks / x);
459 if (ntanks % x) {
460 y = y + 1;
461 }
462 gameSize[0] = x * SPACING;
463 gameSize[1] = y * SPACING;
464
465 // Shuffle the order we place things on the game board.
466 var order = [];
467 for (var i = 0; i < ntanks; i++) {
468 order.push(i);
469 }
470 for (var i = 0; i < ntanks; i++) {
471 var j = Math.floor(Math.random() * ntanks);
472 var n = order[j];
473 order[j] = order[i];
474 order[i] = n;
475 }
476
477 // Position tanks.
478 x = SPACING / 2;
479 y = SPACING / 2;
480 for (var i = 0; i < ntanks; i++) {
481 tanks[order[i]].position[0] = x;
482 tanks[order[i]].position[1] = y;
483 // TODO: Move to constructor?
484 tanks[order[i]].angle = Math.random() * TAU;
485 tanks[order[i]].turret.current = Math.random() * TAU;
486 tanks[order[i]].turret.desired = tanks[order[i]].turret.current;
487
488 x = x + SPACING;
489 if (x > gameSize[0]) {
490 x = x % gameSize[0];
491 y = y + SPACING;
492 }
493 }
494};
495
496var rotate_point = function(angle, point) {
497 var cos_ = Math.cos(angle);
498 var sin_ = Math.sin(angle);
499
500 var newp = [0, 0];
501 newp[0] = point[0]*cos_ - point[1]*sin_;
502 newp[1] = point[0]*sin_ + point[1]*cos_;
503
504 point[0] = newp[0];
505 point[1] = newp[1];
506};
507
508ForfTank.prototype.fireCannon = function(that, vector, dist2) {
509 /* If someone's a crater, this is easy */
510 if ((this.killer && this.killer !== that) || that.killer) {
511 return;
512 }
513
514 /* Did they collide? */
515 if ((!this.killer) && dist2 < TANK_COLLISION_ADJ2) {
516 this.killer = that;
517 this.cause_death = "collision";
518
519 that.killer = this;
520 that.cause_death = "collision";
521
522 return;
523 }
524
525 /* No need to check if it's not even firing */
526 if (! this.turret.firing) {
527 return;
528 }
529
530 /* Also no need to check if it's outside cannon range */
531 if (dist2 > TANK_CANNON_ADJ2) {
532 return;
533 }
534
535 var theta = this.angle + this.turret.current;
536
537 /* Did this shoot that? Rotate point by turret degrees, and if |y| <
538 TANK_RADIUS, we have a hit. */
539 var rpos = [vector[0], vector[1]];
540 rotate_point(-theta, rpos);
541 if ((rpos[0] > 0) && (Math.abs(rpos[1]) < TANK_RADIUS)) {
542 that.killer = this;
543 that.cause_death = "shot";
544 }
545};
546
547ForfTank.prototype.sensorCalc = function(that, vector, dist2) {
548 /* If someone's a crater, this is easy */
549 if (this.killer || that.killer) {
550 return;
551 }
552
553 /* If they're not inside the max sensor, just skip it */
554 if (dist2 > TANK_SENSOR_ADJ2) {
555 return;
556 }
557
558 /* Calculate sensors */
559 for (var i = 0; i < this.sensors.length; i++) {
560 if (0 === this.sensors[i].range) {
561 /* Sensor doesn't exist */
562 continue;
563 }
564
565 /* No need to re-check this sensor if it's already firing */
566 if (this.sensors[i].triggered) {
567 continue;
568 }
569
570 /* If the tank is out of range, don't bother */
571 if (dist2 > sq(this.sensors[i].range + TANK_RADIUS)) {
572 continue;
573 }
574
575 /* What is the angle of our sensor? */
576 var theta = this.angle + this.sensors[i].angle;
577 if (this.sensors[i].turret) {
578 theta += this.turret.current;
579 }
580
581 /* Rotate their position by theta */
582 var rpos = [vector[0], vector[1]];
583 rotate_point(-theta, rpos);
584
585 /* Sensor is symmetrical, we can consider only top quadrants */
586 rpos[1] = Math.abs(rpos[1]);
587
588 /* Compute inverse slopes to tank and of our sensor */
589 var m_s = 1 / Math.tan(this.sensors[i].width / 2);
590 var m_r = rpos[0] / rpos[1];
591
592 /* If our inverse slope is less than theirs, they're inside the arc */
593 if (m_r >= m_s) {
594 this.sensors[i].triggered = 1;
595 continue;
596 }
597
598 /* Now check if the edge of the arc intersects the tank. Do this
599 just like with firing. */
600 rotate_point(this.sensors[i].width / -2, rpos);
601 if ((rpos[0] > 0) && (Math.abs(rpos[1]) < TANK_RADIUS)) {
602 this.sensors[i].triggered = 1;
603 }
604 }
605};
606
607var compute_vector = function(vector, _this, that) {
608 /* Establish shortest vector from center of this to center of that,
609 * taking wrapping into account */
610 for (var i = 0; i < 2; i += 1) {
611 var halfsize = gameSize[i] / 2;
612
613 vector[i] = that.position[i] - _this.position[i];
614 if (vector[i] > halfsize) {
615 vector[i] = vector[i] - gameSize[i];
616 } else if (vector[i] < -halfsize) {
617 vector[i] = gameSize[i] + vector[i];
618 }
619 }
620
621 /* Compute distance^2 for range comparisons */
622 return sq(vector[0]) + sq(vector[1]);
623};
624
625var updateTanks = function(tanks) {
626 /* Charge cannons and reset sensors */
627 for (var i = 0; i < tanks.length; i++) {
628 if (tanks[i].turret.firing) {
629 tanks[i].turret.firing = 0;
630 tanks[i].turret.recharge = TANK_CANNON_RECHARGE;
631 }
632 if (tanks[i].killer) {
633 continue;
634 }
635 if (tanks[i].turret.recharge) {
636 tanks[i].turret.recharge -= 1;
637 }
638 for (var j = 0; j < tanks[i].sensors.length; j += 1) {
639 tanks[i].sensors[j].triggered = 0;
640 }
641 }
642
643 /* Move tanks */
644 for (var i = 0; i < tanks.length; i++) {
645 if (tanks[i].killer) {
646 continue;
647 }
648 tanks[i].move();
649 }
650
651 /* Probe sensors */
652 for (var i = 0; i < tanks.length; i++) {
653 if (tanks[i].killer) {
654 continue;
655 }
656 for (var j = i + 1; j < tanks.length; j += 1) {
657 var _this = tanks[i];
658 var that = tanks[j];
659
660 var vector = [0, 0];
661 var dist2 = compute_vector(vector, _this, that);
662 _this.sensorCalc(that, vector, dist2);
663 vector[0] = -vector[0];
664 vector[1] = -vector[1];
665 that.sensorCalc(_this, vector, dist2);
666 }
667 }
668
669 /* Run programs */
670 var errors = [];
671 for (var i = 0; i < tanks.length; i++) {
672 if (tanks[i].killer) {
673 continue;
674 }
675 try {
676 tanks[i].parse(tanks[i].code);
677 tanks[i].run();
678 } catch (e) {
679 errors.push(e);
680 }
681 }
682 if (errors.length) {
683 if (interval) {
684 clearInterval(interval);
685 }
686
687 document.getElementById('debug').innerHTML = "Error: " + errors.join();
688 return;
689 }
690
691 /* Fire cannons and check for crashes */
692 for (var i = 0; i < tanks.length; i++) {
693 if (tanks[i].killer) {
694 continue;
695 }
696 for (var j = i + 1; j < tanks.length; j += 1) {
697 var _this = tanks[i];
698 var that = tanks[j];
699
700 var vector = [0, 0];
701 var dist2 = compute_vector(vector, _this, that);
702 _this.fireCannon(that, vector, dist2);
703 vector[0] = -vector[0];
704 vector[1] = -vector[1];
705 that.fireCannon(_this, vector, dist2);
706 }
707 }
708};
709
710var addBerzerker = function() {
711 var tank = new ForfTank();
712 tank.init("2 random 0 = { 50 100 set-speed! } { 100 50 set-speed! } ifelse 4 random 0 = { 360 random set-turret! } if 30 random 0 = { fire! } if");
713 ftanks.push(tank);
714};
715
716var resetTanks = function() {
717 if (interval) {
718 clearInterval(interval);
719 }
720
721 document.getElementById('debug').innerHTML = " ";
722
723 tanks = [];
724 ftanks = [];
725 var tank;
726
727 // add the user's tank
728 tank = new ForfTank();
729 tank.color = document.getElementsByName('color')[0].value;
730 for (var i = 0; i < 10; i++) {
731 var range = 1*document.getElementsByName('s'+i+'r')[0].value;
732 var angle = (1*document.getElementsByName('s'+i+'a')[0].value) % 360;
733 var width = (1*document.getElementsByName('s'+i+'w')[0].value) % 360;
734 var turret = 1*document.getElementsByName('s'+i+'t')[0].checked;
735 if (range) {
736 tank.addSensor(range, angle, width, turret);
737 }
738 }
739 var code = document.getElementById('program').value;
740 tank.init(code);
741 ftanks.push(tank);
742
743 var n = 6 + Math.floor(Math.random()*3);
744 for (var i = 0; i < n; i++) {
745 addBerzerker();
746 }
747
748 initTanks(ftanks);
749
750 var canvas = document.getElementById('battlefield');
751 canvas.width = gameSize[0];
752 canvas.height = gameSize[1];
753 var ctx = canvas.getContext('2d');
754 for (var i = 0; i < ftanks.length; i++) {
755 var sensors = [];
756 for (var j = 0; j < ftanks[i].sensors.length; j++) {
757 var s = ftanks[i].sensors[j];
758 var sensor = [s.range, s.angle, s.width, s.turret];
759 sensors.push(sensor);
760 }
761 tank = new Tank(ctx, canvas.width, canvas.height, ftanks[i].color, sensors);
762 tanks.push(tank);
763 }
764
765 function update() {
766 updateTanks(ftanks);
767
768 // clear
769 canvas.width = canvas.width;
770
771 var activeTanks = 0;
772 for (var i = 0; i < ftanks.length; i++) {
773 var flags = 0;
774 if (ftanks[i].turret.firing) {
775 flags |= 1;
776 }
777 if (ftanks[i].led) {
778 flags |= 2;
779 }
780 if (ftanks[i].killer) {
781 flags |= 4;
782 } else {
783 activeTanks++;
784 }
785 var sensor_state = 0;
786 for (var j = 0; j < ftanks[i].sensors.length; j++) {
787 if (ftanks[i].sensors[j].triggered) {
788 sensor_state |= (1 << j);
789 }
790 }
791 tanks[i].set_state(ftanks[i].position[0], ftanks[i].position[1], ftanks[i].angle, ftanks[i].turret.current, flags, sensor_state);
792 }
793
794 if (activeTanks < 2) {
795 // we're done
796 clearInterval(interval);
797 interval = null;
798 }
799
800 for (var i = 0; i < ftanks.length; i++) {
801 tanks[i].draw_crater();
802 }
803
804 for (var i = 0; i < tanks.length; i++) {
805 tanks[i].draw_wrap_sensors();
806 }
807
808 for (var i = 0; i < tanks.length; i++) {
809 tanks[i].draw_tank();
810 }
811 }
812
813 interval = setInterval(update, 100 /*66*/);
814};
815