tanks

Blow up enemy tanks using code
git clone https://git.woozle.org/neale/tanks.git

tanks / docs / _site / assets / js
Neale Pickett  ·  2024-12-05

tanks.js

  1function dbg(o) {
  2    e = document.getElementById("debug");
  3    e.innerHTML = o;
  4}
  5
  6function torgba(color, alpha) {
  7    var r = parseInt(color.substring(1,3), 16);
  8    var g = parseInt(color.substring(3,5), 16);
  9    var b = parseInt(color.substring(5,7), 16);
 10
 11    return "rgba(" + r + "," + g + "," + b + "," + alpha + ")";
 12}
 13
 14function Tank(ctx, width, height, color, sensors) {
 15    var craterStroke = torgba(color, 0.5);
 16    var craterFill = torgba(color, 0.2);
 17    var sensorStroke = torgba(color, 0.4);
 18    var maxlen = 0;
 19
 20    this.x = 0;
 21    this.y = 0;
 22    this.rotation = 0;
 23    this.turret = 0;
 24
 25    this.dead = 0;
 26
 27    // Do all the yucky math up front
 28    this.sensors = new Array();
 29    for (i in sensors) {
 30        var s = sensors[i];
 31
 32        if (! s) {
 33            this.sensors[i] = [0,0,0,0];
 34        } else {
 35            // r, angle, width, turret
 36            this.sensors[i] = new Array();
 37            this.sensors[i][0] = s[0];
 38            this.sensors[i][1] = s[1] - s[2]/2;
 39            this.sensors[i][2] = s[1] + s[2]/2;
 40            this.sensors[i][3] = s[3]?1:0;
 41            if (s[0] > maxlen) {
 42                maxlen = s[0];
 43            }
 44        }
 45    }
 46
 47    // Set up our state, for later interleaved draw requests
 48    this.set_state = function(x, y, rotation, turret, flags, sensor_state) {
 49        this.x = x;
 50        this.y = y;
 51        this.rotation = rotation;
 52        this.turret = turret;
 53        if (flags & 1) {
 54            this.fire = 5;
 55        }
 56        this.led = flags & 2;
 57        this.dead = flags & 4;
 58        this.sensor_state = sensor_state;
 59    }
 60
 61    this.draw_crater = function() {
 62        if (!this.dead) {
 63            return;
 64        }
 65
 66        var points = 7;
 67        var angle = Math.PI / points;
 68
 69        ctx.save();
 70        ctx.translate(this.x, this.y);
 71        ctx.rotate(this.rotation);
 72
 73        if (this.fire == 5) {
 74            ctx.save();
 75            ctx.rotate(this.turret);
 76            // one frame of cannon fire
 77            this.draw_cannon();
 78            this.fire = 0;
 79            ctx.restore();
 80        }
 81
 82        ctx.lineWidth = 2;
 83        ctx.strokeStyle = craterStroke;
 84        ctx.fillStyle = craterFill;
 85        ctx.beginPath();
 86        ctx.moveTo(12, 0);
 87        for (i = 0; i < points; i += 1) {
 88            ctx.rotate(angle);
 89            ctx.lineTo(6, 0);
 90            ctx.rotate(angle);
 91            ctx.lineTo(12, 0);
 92        }
 93        ctx.closePath()
 94        ctx.stroke();
 95        ctx.fill();
 96
 97        ctx.restore();
 98    }
 99
100    this.draw_sensors = function() {
101        if (this.dead) {
102            return;
103        }
104        ctx.save();
105        ctx.translate(this.x, this.y);
106        ctx.rotate(this.rotation);
107
108        ctx.lineWidth = 1;
109        for (i in this.sensors) {
110            var s = this.sensors[i];
111            var adj = this.turret * s[3];
112
113            if (this.sensor_state & (1 << i)) {
114                // Sensor is triggered
115                ctx.strokeStyle = "#000";
116            } else {
117                ctx.strokeStyle = sensorStroke;
118            }
119            ctx.beginPath();
120            ctx.moveTo(0, 0);
121            ctx.arc(0, 0, s[0], s[1] + adj, s[2] + adj, false);
122            ctx.closePath();
123            ctx.stroke();
124        }
125
126        ctx.restore();
127    }
128
129    this.draw_tank = function() {
130        if (this.dead) {
131            return;
132        }
133        ctx.save();
134        ctx.translate(this.x, this.y);
135        ctx.rotate(this.rotation);
136
137        ctx.fillStyle = color;
138        ctx.fillRect(-5, -4, 10, 8);
139        ctx.fillStyle = "#777";
140        ctx.fillRect(-7, -9, 15, 5);
141        ctx.fillRect(-7,  4, 15, 5);
142        ctx.rotate(this.turret);
143        if (this.fire) {
144            this.draw_cannon();
145            this.fire -= 1;
146        } else {
147            if (this.led) {
148                ctx.fillStyle = "#f00";
149            } else {
150                ctx.fillStyle = "#000";
151            }
152            ctx.fillRect(0, -1, 10, 2);
153        }
154
155        ctx.restore();
156    }
157
158    this.draw_cannon = function() {
159        ctx.fillStyle = ("rgba(255,255,64," + this.fire/5 + ")");
160        ctx.fillRect(0, -1, 45, 2);
161    }
162
163    this.draw_wrap_sensors = function() {
164        var orig_x = this.x;
165        var orig_y = this.y;
166        for (x = this.x - width; x < width + maxlen; x += width) {
167            for (y = this.y - height; y < height + maxlen; y += height) {
168                if ((-maxlen < x) && (x < width + maxlen) &&
169                    (-maxlen < y) && (y < height + maxlen)) {
170                    this.x = x;
171                    this.y = y;
172                    this.draw_sensors();
173                }
174            }
175        }
176        this.x = orig_x;
177        this.y = orig_y;
178    }
179}
180
181var loop_id;
182var updateFunc = null;
183function togglePlayback() {
184    if ($("#playing").prop("checked")) {
185        loop_id = setInterval(updateFunc, 66);
186    } else {
187        clearInterval(loop_id);
188        loop_id = null;
189    }
190    $("#pauselabel").toggleClass("ui-icon-play ui-icon-pause");
191}
192
193function start(id, game) {
194    var canvas = document.getElementById(id);
195    var ctx = canvas.getContext('2d');
196
197    canvas.width = game[0][0];
198    canvas.height = game[0][1];
199    // game[2] is tank descriptions
200    var turns = game[2];
201
202    // Set up tanks
203    var tanks = new Array();
204    for (i in game[1]) {
205        var desc = game[1][i];
206        tanks[i] = new Tank(ctx, game[0][0], game[0][1], desc[0], desc[1]);
207    }
208
209    var frame = 0;
210    var lastframe = 0;
211    var fps = document.getElementById('fps');
212
213    function update_fps() {
214        fps.innerHTML = (frame - lastframe);
215        lastframe = frame;
216    }
217
218    function drawFrame(idx) {
219        canvas.width = canvas.width;
220        turn = turns[idx];
221
222        // Update and draw craters first
223        for (i in turn) {
224            t = turn[i];
225            if (!t) {
226                // old data, force-kill it
227                tanks[i].fire = 0;
228                tanks[i].dead = 5;
229            } else {
230                tanks[i].set_state(t[0], t[1], t[2], t[3], t[4], t[5]);
231            }
232            tanks[i].draw_crater();
233        }
234        // Then sensors
235        for (i in turn) {
236            tanks[i].draw_wrap_sensors();
237        }
238        // Then tanks
239        for (i in turn) {
240            tanks[i].draw_tank()
241        }
242
243        document.getElementById('frameid').innerHTML = idx;
244    }
245
246    function update() {
247        var idx = frame % (turns.length + 20);
248        var turn;
249
250        frame += 1;
251        if (idx >= turns.length) {
252            return;
253        }
254
255        drawFrame(idx);
256
257        $('#seekslider').slider('value', idx);
258    }
259
260    function seekToFrame(newidx) {
261        var idx = frame % (turns.length + 20);
262        if (idx !== newidx) {
263            frame = newidx;
264            drawFrame(newidx);
265        }
266        // make sure we're paused
267        if ($("#playing").prop("checked")) {
268            $("#playing").prop("checked", false);
269            togglePlayback();
270        }
271    }
272
273    updateFunc = update;
274    loop_id = setInterval(update, 66);
275    //loop_id = setInterval(update, 400);
276    if (fps) {
277        setInterval(update_fps, 1000);
278    }
279
280    if (id === "battlefield") {
281        $("#game_box").append('<p><input type="checkbox" checked id="playing" onclick="togglePlayback();"><label for="playing"><span class="ui-icon ui-icon-pause" id="pauselabel"></class></label> <span id="frameid">0</span> <span id="seekslider" style="width: 75%; float: right;"></span></p>');
282        $('#playing').button();
283        var slider = $('#seekslider');
284        slider.slider({ max: turns.length-1, slide: function(event, ui) { seekToFrame(ui.value); } });
285
286        var spacing = 100 / turns.length;
287        var deaths = [];
288        for (i in turns[0]) {
289            deaths.push(false);
290        }
291        var percent = 0;
292        for (var f = 0; f < turns.length; f++) {
293            var turn = turns[f];
294            if (percent < (spacing * f)) {
295                percent = spacing * f;
296            }
297            for (var i = 0; i < turn.length; i++) {
298                if (deaths[i]) { continue; }
299                if (!turn[i] || (turn[i][4] & 4)) {
300                    deaths[i] = true;
301                    // http://stackoverflow.com/questions/8648963/add-tick-marks-to-jquery-slider
302                    $('<span class="ui-slider-tick-mark"></span>').css('left', percent +  '%').css('background-color', game[1][i][0]).appendTo(slider);
303                    percent++;
304                    break;
305                }
306            }
307        }
308    }
309}
310