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