tanks

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

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}