Neale Pickett
·
2024-12-04
ctanks.c
1#include "ctanks.h"
2#include <math.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6
7/* Debugging help */
8#define DUMPf(fmt, args...) \
9 fprintf(stderr, "%s:%s:%d " fmt "\n", __FILE__, __FUNCTION__, __LINE__, \
10 ##args)
11#define DUMP() DUMPf("")
12#define DUMP_d(v) DUMPf("%s = %d", #v, v)
13#define DUMP_x(v) DUMPf("%s = 0x%x", #v, v)
14#define DUMP_s(v) DUMPf("%s = %s", #v, v)
15#define DUMP_c(v) DUMPf("%s = %c", #v, v)
16#define DUMP_f(v) DUMPf("%s = %f", #v, v)
17#define DUMP_p(v) DUMPf("%s = %p", #v, v)
18#define DUMP_xy(v) DUMPf("%s = (%f, %f)", #v, v[0], v[1]);
19#define DUMP_angle(v) DUMPf("%s = %.3fτ", #v, (v / TAU));
20
21#define sq(x) ((x) * (x))
22
23void tank_init(struct tank *tank, tank_run_func *run, void *udata) {
24 memset(tank, 0, sizeof(*tank));
25 tank->run = run;
26 tank->udata = udata;
27}
28
29int tank_fire_ready(struct tank *tank) { return (!tank->turret.recharge); }
30
31void tank_fire(struct tank *tank) {
32 tank->turret.firing = tank_fire_ready(tank);
33}
34
35void tank_set_speed(struct tank *tank, float left, float right) {
36 tank->speed.desired[0] = min(max(left, -100), 100);
37 tank->speed.desired[1] = min(max(right, -100), 100);
38}
39
40float tank_get_turret(struct tank *tank) { return tank->turret.current; }
41
42void tank_set_turret(struct tank *tank, float angle) {
43 tank->turret.desired = fmodf(angle, TAU);
44}
45
46int tank_get_sensor(struct tank *tank, int sensor_num) {
47 if ((sensor_num < 0) || (sensor_num > TANK_MAX_SENSORS)) {
48 return 0;
49 }
50 return tank->sensors[sensor_num].triggered;
51}
52
53void tank_set_led(struct tank *tank, int active) { tank->led = active; }
54
55static void rotate_point(float angle, float point[2]) {
56 float cos_, sin_;
57 float new[2];
58
59 cos_ = cosf(angle);
60 sin_ = sinf(angle);
61
62 new[0] = point[0] * cos_ - point[1] * sin_;
63 new[1] = point[0] * sin_ + point[1] * cos_;
64
65 point[0] = new[0];
66 point[1] = new[1];
67}
68
69static void tanks_fire_cannon(struct tanks_game *game, struct tank *this,
70 struct tank *that, float vector[2], float dist2) {
71 float theta = this->angle + this->turret.current;
72 float rpos[2];
73
74 /* If someone's a crater, this is easy (unless we were just killed by the
75 other one, in which case we have to check the other direction) */
76 if ((this->killer && this->killer != that) || that->killer) {
77 return;
78 }
79
80 /* Did they collide? */
81 if ((!this->killer) && dist2 < TANK_COLLISION_ADJ2) {
82 this->killer = that;
83 this->cause_death = "collision";
84
85 that->killer = this;
86 that->cause_death = "collision";
87
88 return;
89 }
90
91 /* No need to check if it's not even firing */
92 if (!this->turret.firing) {
93 return;
94 }
95
96 /* Also no need to check if it's outside cannon range */
97 if (dist2 > TANK_CANNON_ADJ2) {
98 return;
99 }
100
101 /* Did this shoot that? Rotate point by turret degrees, and if |y| <
102 TANK_RADIUS, we have a hit. */
103 rpos[0] = vector[0];
104 rpos[1] = vector[1];
105 rotate_point(-theta, rpos);
106 if ((rpos[0] > 0) && (fabsf(rpos[1]) < TANK_RADIUS)) {
107 that->killer = this;
108 that->cause_death = "shot";
109 }
110}
111
112static void tanks_sensor_calc(struct tanks_game *game, struct tank *this,
113 struct tank *that, float vector[2], float dist2) {
114 int i;
115
116 /* If someone's a crater, this is easy */
117 if (this->killer || that->killer) {
118 return;
119 }
120
121 /* If they're not inside the max sensor, just skip it */
122 if (dist2 > TANK_SENSOR_ADJ2) {
123 return;
124 }
125
126 /* Calculate sensors */
127 for (i = 0; i < TANK_MAX_SENSORS; i += 1) {
128 float theta;
129 float rpos[2];
130 float m_r, m_s;
131
132 if (0 == this->sensors[i].range) {
133 /* Sensor doesn't exist */
134 continue;
135 }
136
137 /* No need to re-check this sensor if it's already firing */
138 if (this->sensors[i].triggered) {
139 continue;
140 }
141
142 /* If the tank is out of range, don't bother */
143 if (dist2 > sq(this->sensors[i].range + TANK_RADIUS)) {
144 continue;
145 }
146
147 /* What is the angle of our sensor? */
148 theta = this->angle + this->sensors[i].angle;
149 if (this->sensors[i].turret) {
150 theta += this->turret.current;
151 }
152
153 /* Rotate their position by theta */
154 rpos[0] = vector[0];
155 rpos[1] = vector[1];
156 rotate_point(-theta, rpos);
157
158 /* Sensor is symmetrical, we can consider only top quadrants */
159 rpos[1] = fabsf(rpos[1]);
160
161 /* Compute inverse slopes to tank and of our sensor */
162 m_s = 1 / tanf(this->sensors[i].width / 2);
163 m_r = rpos[0] / rpos[1];
164
165 /* If our inverse slope is less than theirs, they're inside the arc */
166 if (m_r >= m_s) {
167 this->sensors[i].triggered = 1;
168 continue;
169 }
170
171 /* Now check if the edge of the arc intersects the tank. Do this
172 just like with firing. */
173 rotate_point(this->sensors[i].width / -2, rpos);
174 if ((rpos[0] > 0) && (fabsf(rpos[1]) < TANK_RADIUS)) {
175 this->sensors[i].triggered = 1;
176 }
177 }
178}
179
180void compute_vector(struct tanks_game *game, float vector[2], float *dist2,
181 struct tank *this, struct tank *that) {
182 int i;
183
184 /* Establish shortest vector from center of this to center of that,
185 * taking wrapping into account */
186 for (i = 0; i < 2; i += 1) {
187 float halfsize = game->size[i] / 2;
188
189 vector[i] = that->position[i] - this->position[i];
190 if (vector[i] > halfsize) {
191 vector[i] = vector[i] - game->size[i];
192 } else if (vector[i] < -halfsize) {
193 vector[i] = game->size[i] + vector[i];
194 }
195 }
196
197 /* Compute distance^2 for range comparisons */
198 *dist2 = sq(vector[0]) + sq(vector[1]);
199}
200
201void tanks_move_tank(struct tanks_game *game, struct tank *tank) {
202 int i;
203 float movement;
204 float angle;
205 int dir = 1;
206
207 /* Rotate the turret */
208 {
209 float rot_angle; /* Quickest way there */
210
211 /* Constrain rot_angle to between -PI and PI */
212 rot_angle = tank->turret.desired - tank->turret.current;
213 while (rot_angle < 0) {
214 rot_angle += TAU;
215 }
216 rot_angle = fmodf(PI + rot_angle, TAU) - PI;
217
218 rot_angle = min(TANK_MAX_TURRET_ROT, rot_angle);
219 rot_angle = max(-TANK_MAX_TURRET_ROT, rot_angle);
220 tank->turret.current = fmodf(tank->turret.current + rot_angle, TAU);
221 }
222
223 /* Fakey acceleration */
224 for (i = 0; i < 2; i += 1) {
225 if (tank->speed.current[i] == tank->speed.desired[i]) {
226 /* Do nothing */
227 } else if (tank->speed.current[i] < tank->speed.desired[i]) {
228 tank->speed.current[i] =
229 min(tank->speed.current[i] + TANK_MAX_ACCEL, tank->speed.desired[i]);
230 } else {
231 tank->speed.current[i] =
232 max(tank->speed.current[i] - TANK_MAX_ACCEL, tank->speed.desired[i]);
233 }
234 }
235
236 /* The simple case */
237 if (tank->speed.current[0] == tank->speed.current[1]) {
238 movement = tank->speed.current[0] * (TANK_TOP_SPEED / 100.0);
239 angle = 0;
240 } else {
241 /* pflarr's original comment:
242 *
243 * The tank drives around in a circle of radius r, which is some
244 * offset on a line perpendicular to the tank. The distance it
245 * travels around the circle varies with the speed of each tread,
246 * and is such that each side of the tank moves an equal angle
247 * around the circle.
248 *
249 * Sounds good to me. pflarr's calculations here are fantastico,
250 * there's nothing whatsoever to change. */
251 float friction;
252 float v[2];
253 float So, Si;
254 float r;
255 float theta;
256
257 /* The first thing Paul's code does is find "friction", which seems
258 to be a penalty for having the treads go in opposite directions.
259 This probably plays hell with precisely-planned tanks, which I
260 find very ha ha. */
261 friction = TANK_FRICTION *
262 (fabsf(tank->speed.current[0] - tank->speed.current[1]) / 200);
263 v[0] = tank->speed.current[0] * (1 - friction) * (TANK_TOP_SPEED / 100.0);
264 v[1] = tank->speed.current[1] * (1 - friction) * (TANK_TOP_SPEED / 100.0);
265
266 /* Outside and inside speeds */
267 if (fabsf(v[0]) > abs(v[1])) {
268 Si = v[1];
269 So = v[0];
270 dir = 1;
271 } else {
272 Si = v[0];
273 So = v[1];
274 dir = -1;
275 }
276
277 /* Radius of circle to outside tread (use similar triangles) */
278 r = So * (TANK_RADIUS * 2) / (So - Si);
279
280 /* pflarr:
281
282 The fraction of the circle traveled is equal to the speed
283 of the outer tread over the circumference of the circle:
284 Ft = So/(tau*r)
285 The angle traveled is:
286 theta = Ft * tau
287 This reduces to a simple
288 theta = So/r
289 We multiply it by dir to adjust for the direction of rotation
290 */
291 theta = So / r * dir;
292
293 movement = r * tanf(theta);
294 angle = theta;
295 }
296
297 /* Now move the tank */
298 tank->angle = fmodf(tank->angle + angle + TAU, TAU);
299 {
300 float m[2];
301
302 m[0] = cosf(tank->angle) * movement * dir;
303 m[1] = sinf(tank->angle) * movement * dir;
304
305 for (i = 0; i < 2; i += 1) {
306 tank->position[i] =
307 fmodf(tank->position[i] + m[i] + game->size[i], game->size[i]);
308 }
309 }
310}
311
312void tanks_run_turn(struct tanks_game *game, struct tank *tanks, int ntanks) {
313 int i, j;
314 float vector[2];
315 float dist2; /* distance squared */
316
317 /* It takes (at least) two to tank-o */
318 if (ntanks < 2) {
319 return;
320 }
321
322 /* Charge cannons and reset sensors */
323 for (i = 0; i < ntanks; i += 1) {
324 if (tanks[i].turret.firing) {
325 tanks[i].turret.firing = 0;
326 tanks[i].turret.recharge = TANK_CANNON_RECHARGE;
327 }
328 if (tanks[i].killer)
329 continue;
330 if (tanks[i].turret.recharge) {
331 tanks[i].turret.recharge -= 1;
332 }
333 for (j = 0; j < TANK_MAX_SENSORS; j += 1) {
334 tanks[i].sensors[j].triggered = 0;
335 }
336 }
337
338 /* Move tanks */
339 for (i = 0; i < ntanks; i += 1) {
340 if (tanks[i].killer)
341 continue;
342 tanks_move_tank(game, &(tanks[i]));
343 }
344
345 /* Probe sensors */
346 for (i = 0; i < ntanks; i += 1) {
347 if (tanks[i].killer)
348 continue;
349 for (j = i + 1; j < ntanks; j += 1) {
350 struct tank *this = &tanks[i];
351 struct tank *that = &tanks[j];
352
353 compute_vector(game, vector, &dist2, this, that);
354 tanks_sensor_calc(game, this, that, vector, dist2);
355 vector[0] = -vector[0];
356 vector[1] = -vector[1];
357 tanks_sensor_calc(game, that, this, vector, dist2);
358 }
359 }
360
361 /* Run programs */
362 for (i = 0; i < ntanks; i += 1) {
363 if (tanks[i].killer)
364 continue;
365 tanks[i].run(&tanks[i], tanks[i].udata);
366 }
367
368 /* Fire cannons and check for crashes */
369 for (i = 0; i < ntanks; i += 1) {
370 if (tanks[i].killer)
371 continue;
372 for (j = i + 1; j < ntanks; j += 1) {
373 struct tank *this = &tanks[i];
374 struct tank *that = &tanks[j];
375
376 compute_vector(game, vector, &dist2, this, that);
377 tanks_fire_cannon(game, this, that, vector, dist2);
378 vector[0] = -vector[0];
379 vector[1] = -vector[1];
380 tanks_fire_cannon(game, that, this, vector, dist2);
381 }
382 }
383}