/*
"Bing" is a quick hack at making a C version of "Boing!", the Amiga bouncing ball.

I was impressed by David Galloway's assembly-coded version of the program and wanted
to confirm my suspicion that this is *not* an easy program to write and make work in
under 30,000 cycles.  After starting with an 'obvious' version of the code and
repeatedly refining it to have it run faster, I've spent a day on it and failed to
match David's performance!

So conclusion affirmed - for a first program on the Vectrex, he did a pretty damn
good job!  In fact it leaves me wondering what optimisations he caught that I missed...

(I don't believe the difference is merely assembler versus C - I'm sure it's an
algorithmic difference and undoubtedly related to whatever drawing mechanism he
uses.  I was using Malban's sync lists mostly which I thought were fairly well
optimised already, but maybe there's better drawing code I should be using,
especially for the grid drawing.)

This version used a sin/cos table to build the ball that stretched the values in
one axis, to compensate for the distorted aspect ratio selected by Vide as default.
This may not match other emulators or your own hardware.

With a table-driven program like this, if you want to support different aspect
ratios at run-time, you need to supply multiple copies of the vectors and have
the user select one from the available choices.  There are three or four common
configurations of the Vectrex screen size to choose from - it's not really a
continuum.  A procedural implementation would allow correction for arbitrary-sized
aspect ratios, but the overhead of a multiply and divide for each vector would
be prohibitively slow.

 */
#include <assert.h>
#include <vectrex.h>
static inline void set_scale(unsigned int scale) {/*VIA_t1_cnt_lo*/ *(volatile unsigned char *)0xD004 = scale;}
#include "ball.h"

#define SQHEIGHT 13
#define SQWIDTH 16

int main(void) {
  int8_t i, bally = 0, ballx = 0, xdelta = 1, ydelta = 1, PHASE = 0, tick = 0;

  for (;;) {
    Wait_Recal();
    set_scale(0xe0); // fit the ball in the screen. Otherwise it may drop off the edge!

    // draw grid:
    Intensity_1F();
    // I did try a custom grid-drawing procedure but it was large and messy and
    // only saved about 200 cycles so I removed it.
    draw_synced_list_c(Across, 0,0, 0xe0, 0xe0);
    draw_synced_list_c(Down, 0,0, 0xe0, 0xe0);

    // Even without the grid, the ball drawing alone initially cost 33200 cycles.
    // by doubling the size of the delta-vectors in the Ball[] array and halving the
    // drawing scale, I was able to get this down to 24240 cycles without significant
    // structural changes.

    // Draw Meridians
    Intensity_7F();
    // four steps in rotation - might look better with 8 ... or maybe 4x6 rather than 4x4 ...
    for (i = 0; i < 16; i += 4) { draw_synced_list_c(Ball[i+(PHASE^((xdelta > 0)?0:3))], bally, ballx, 0xe0, 0x70); }
                                                             //                 ^ rotation direction
    // Draw Outer ball boundary
    draw_synced_list_c(Ball[16], bally, ballx, 0xe0, 0x70); // leftmost
    draw_synced_list_c(Ball[25], bally, ballx, 0xe0, 0x70); // rightmost 

    // draw latitudes
    for (i = 17; i <= 24; i++) { draw_synced_list_c(Ball[i], bally, ballx, 0xe0, 0x70); }

    // perform animation:
    if ((tick & 3) == 3) /* tick speed 3 7 15 */ PHASE = (PHASE + 1)&3;

    // Haven't written code to do more realistic (but lossless) bounce:
    // parameters: never reaches top
    // bounce off bottom reverses vertical direction, leaves horizontal velocity untouched
    // bounce off side reverses horizontal direction, leaves vertical velocity untouched
    // but for some reason reverses rotation of ball, like a bizarro-world superball.
    // Only significant difference is linear vertical velocity versus effect of gravity...

    bally = bally + ydelta;

    // to add gravity, create a lookup table and use something like this line instead:
    // bally = bally + ydelta[bally];
    // with higher heights having lower deltas

    ballx = ballx + xdelta;
    if (bally <= -5*SQHEIGHT) { ydelta = -ydelta; } else if (bally >= 5*SQHEIGHT) { ydelta = -ydelta; }
    if (ballx <= -3*SQWIDTH)  { xdelta = -xdelta; } else if (ballx >= 3*SQWIDTH)  { xdelta = -xdelta; }

    tick += 1;
  }
  return 0;
}

