#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <pgmspace.h>

#define TFT_CS    D0
#define TFT_RST   D2
#define TFT_DC    D1
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

/* button setup for Arduboy Production */
uint8_t pin_up = D4;
uint8_t pin_down = D5;
uint8_t pin_fire = D6;

#define ST_FP 4

/* object types */
struct _st_ot_struct
{
  uint8_t missle_mask;
  uint8_t hit_mask;
  uint8_t points;
  uint8_t draw_fn;
  uint8_t move_fn;
  uint8_t destroy_fn;
  uint8_t is_hit_fn;
  uint8_t fire_fn;
};
typedef struct _st_ot_struct st_ot;

struct _st_obj_struct
{
  uint8_t ot;
  int8_t tmp;
  int16_t x, y;
  int8_t x0, y0, x1, y1;
};
typedef struct _st_obj_struct st_obj;

#define ST_DRAW_NONE 0
#define ST_DRAW_BBOX 1
#define ST_DRAW_TRASH1 2
#define ST_DRAW_PLAYER1 3
#define ST_DRAW_TRASH2 4
#define ST_DRAW_PLAYER2 5
#define ST_DRAW_PLAYER3 6
#define ST_DRAW_GADGET 7
#define ST_DRAW_BACKSLASH 8
#define ST_DRAW_SLASH 9
#define ST_DRAW_BIG_TRASH 10

#define ST_MOVE_NONE 0
#define ST_MOVE_X_SLOW 1
#define ST_MOVE_PX_NORMAL 2
#define ST_MOVE_PX_FAST 3
#define ST_MOVE_PLAYER 4
#define ST_MOVE_PY 5
#define ST_MOVE_NY 6
#define ST_MOVE_IMPLODE 7
#define ST_MOVE_X_FAST 8
#define ST_MOVE_WALL 9
#define ST_MOVE_NXPY 10
#define ST_MOVE_NXNY 11

#define ST_IS_HIT_NONE 0
#define ST_IS_HIT_BBOX 1
#define ST_IS_HIT_WALL 2

#define ST_DESTROY_NONE 0
#define ST_DESTROY_DISAPPEAR 1
#define ST_DESTROY_TO_DUST 2
#define ST_DESTROY_GADGET 3
#define ST_DESTROY_PLAYER 4
#define ST_DESTROY_PLAYER_GADGETS 5
#define ST_DESTROY_BIG_TRASH 6

#define ST_FIRE_NONE 0
#define ST_FIRE_PLAYER1 1
#define ST_FIRE_PLAYER2 2
#define ST_FIRE_PLAYER3 3

#define ST_OT_WALL_SOLID 1
#define ST_OT_BIG_TRASH 2
#define ST_OT_MISSLE 3
#define ST_OT_TRASH1 4
#define ST_OT_PLAYER 5
#define ST_OT_DUST_PY 6
#define ST_OT_DUST_NY 7
#define ST_OT_TRASH_IMPLODE 8
#define ST_OT_TRASH2 9
#define ST_OT_PLAYER2 10
#define ST_OT_PLAYER3 11
#define ST_OT_GADGET 12
#define ST_OT_GADGET_IMPLODE 13
#define ST_OT_DUST_NXPY 14
#define ST_OT_DUST_NXNY 15

st_obj st_objects[60];

uint8_t st_player_pos;
#define ST_POINTS_PER_LEVEL 25
uint16_t st_player_points;
uint16_t st_player_points_delayed;
uint16_t st_highscore = 0;

#define ST_STATE_PREPARE 0
#define ST_STATE_IPREPARE 1
#define ST_STATE_GAME 2
#define ST_STATE_END 3
#define ST_STATE_IEND 4

uint8_t st_state = ST_STATE_PREPARE;

uint8_t st_difficulty = 1;
#define ST_DIFF_VIS_LEN 30
#define ST_DIFF_FP 5
uint16_t st_to_diff_cnt = 0;

const st_ot st_object_types[] PROGMEM =
{
  { 0, 0,  0, ST_DRAW_NONE, ST_MOVE_NONE, ST_DESTROY_DISAPPEAR, ST_IS_HIT_NONE, ST_FIRE_NONE },
  { 2, 1, 30, ST_DRAW_BBOX, ST_MOVE_WALL, ST_DESTROY_DISAPPEAR, ST_IS_HIT_WALL, ST_FIRE_NONE },
  { 2, 1,  0, ST_DRAW_BIG_TRASH, ST_MOVE_X_SLOW, ST_DESTROY_BIG_TRASH, ST_IS_HIT_BBOX, ST_FIRE_NONE },
  { 1, 0,  0, ST_DRAW_BBOX, ST_MOVE_PX_FAST, ST_DESTROY_DISAPPEAR, ST_IS_HIT_NONE, ST_FIRE_NONE },
  { 2, 1,  0, ST_DRAW_TRASH1, ST_MOVE_X_SLOW, ST_DESTROY_TO_DUST, ST_IS_HIT_BBOX, ST_FIRE_NONE },
  { 0, 2,  0, ST_DRAW_PLAYER1, ST_MOVE_PLAYER, ST_DESTROY_PLAYER, ST_IS_HIT_BBOX, ST_FIRE_PLAYER1 },
  { 0, 0,  0, ST_DRAW_BBOX, ST_MOVE_PY, ST_DESTROY_NONE, ST_IS_HIT_NONE, ST_FIRE_NONE },
  { 0, 0,  0, ST_DRAW_BBOX, ST_MOVE_NY, ST_DESTROY_NONE, ST_IS_HIT_NONE, ST_FIRE_NONE },
  { 0, 0,  5, ST_DRAW_TRASH1, ST_MOVE_IMPLODE, ST_DESTROY_NONE, ST_IS_HIT_NONE, ST_FIRE_NONE },
  { 2, 1,  0, ST_DRAW_TRASH2, ST_MOVE_X_SLOW, ST_DESTROY_TO_DUST, ST_IS_HIT_BBOX, ST_FIRE_NONE },
  { 0, 2,  0, ST_DRAW_PLAYER2, ST_MOVE_PLAYER, ST_DESTROY_PLAYER_GADGETS, ST_IS_HIT_BBOX, ST_FIRE_PLAYER2 },
  { 0, 2,  0, ST_DRAW_PLAYER3, ST_MOVE_PLAYER, ST_DESTROY_PLAYER_GADGETS, ST_IS_HIT_BBOX, ST_FIRE_PLAYER3 },
  { 0, 1,  0, ST_DRAW_GADGET, ST_MOVE_X_FAST, ST_DESTROY_GADGET, ST_IS_HIT_BBOX, ST_FIRE_NONE },
  { 0, 0, 20, ST_DRAW_GADGET, ST_MOVE_IMPLODE, ST_DESTROY_NONE, ST_IS_HIT_NONE, ST_FIRE_NONE },
  { 0, 0,  0, ST_DRAW_BACKSLASH, ST_MOVE_NXPY, ST_DESTROY_NONE, ST_IS_HIT_NONE, ST_FIRE_NONE },
  { 0, 0,  0, ST_DRAW_SLASH, ST_MOVE_NXNY, ST_DESTROY_NONE, ST_IS_HIT_NONE, ST_FIRE_NONE },
};

const uint8_t st_bitmap_player1[] PROGMEM = 
{ 
  0x060,
  0x0f8,
  0x07e,
  0x0f8,
  0x060
};

const uint8_t st_bitmap_player2[] PROGMEM = 
{   
  0x060,
  0x078,
  0x060,
  0x0e0,
  0x0f8,
  0x07e,
  0x0f8,
  0x060
};

const uint8_t st_bitmap_player3[] PROGMEM = 
{   
  0x060,
  0x078,
  0x060,
  0x0e0,
  0x0f8,
  0x07e,
  0x0f8,
  0x0e0,
  0x060,
  0x078,
  0x060
 };

const uint8_t st_bitmap_trash_5x5_1[] PROGMEM = 
{ 
  0x070,
  0x0f0,
  0x0f8,
  0x078,
  0x030,
};

const uint8_t st_bitmap_trash_5x5_2[] PROGMEM = 
{ 
  0x030,
  0x0f8,
  0x0f8,
  0x0f0,
  0x070,
};

const uint8_t st_bitmap_trash_7x7[] PROGMEM = 
{
  0x038,
  0x07c,
  0x0fc,
  0x0fe,
  0x0fe,
  0x07e,
  0x078,
};

const uint8_t st_bitmap_gadget[] PROGMEM = 
{ 
  0x070,
  0x0d8,
  0x088,
  0x0d8,
  0x070,
};

char st_itoa_buf[12];
char *st_itoa(unsigned long v)
{
  volatile unsigned char i = 11;
  st_itoa_buf[11] = '\0';
  while( i > 0)
  {
      i--;
      st_itoa_buf[i] = (v % 10)+'0';
      v /= 10;
      if ( v == 0 )
        break;
  }
  return st_itoa_buf+i;
}

uint8_t st_rnd(void)
{
  return rand();
}

static st_obj *st_GetObj(uint8_t objnr)
{
  return st_objects+objnr;
}

uint8_t st_GetMissleMask(uint8_t objnr)
{
  st_obj *o = st_GetObj(objnr);
  return pgm_read_byte(&(st_object_types[o->ot].missle_mask));
}

uint8_t st_GetHitMask(uint8_t objnr)
{
  st_obj *o = st_GetObj(objnr);
  return pgm_read_byte(&(st_object_types[o->ot].hit_mask));
}

int8_t st_FindObj(uint8_t ot)
{
  int8_t i;
  for( i = 0; i < 60; i++ )
  {
    if ( st_objects[i].ot == ot )
      return i;
  }
  return -1;
}

void st_ClrObjs(void)
{
  int8_t i;
  for( i = 0; i < 60; i++ )
    st_objects[i].ot = 0;
}

int8_t st_NewObj(void)
{
  int8_t i;
  for( i = 0; i < 60; i++ )
  {
    if ( st_objects[i].ot == 0 )
      return i;
  }
  return -1;
}

uint8_t st_CntObj(uint8_t ot)
{
  uint8_t i;
  uint8_t cnt = 0;
  for( i = 0; i < 60; i++ )
  {
    if ( st_objects[i].ot == ot )
      cnt++;
  }
  return cnt;
}

uint8_t st_px_x, st_px_y;
uint8_t st_CalcXY(st_obj *o)
{
  st_px_y = o->y>>ST_FP;
  st_px_x = o->x>>ST_FP;
  return st_px_x;
}

void st_SetXY(st_obj *o, uint8_t x, uint8_t y)
{
  o->x = ((int16_t)x) << ST_FP;
  o->y = ((int16_t)y) << ST_FP;
}

int16_t st_bbox_x0, st_bbox_y0, st_bbox_x1, st_bbox_y1;

void st_CalcBBOX(uint8_t objnr)
{
  st_obj *o = st_GetObj(objnr);
  
  st_bbox_x0 = (uint16_t)(o->x>>ST_FP);
  st_bbox_x1 = st_bbox_x0;
  st_bbox_x0 += o->x0;
  st_bbox_x1 += o->x1;

  st_bbox_y0 = (uint16_t)(o->y>>ST_FP);
  st_bbox_y1 = st_bbox_y0;
  st_bbox_y0 += o->y0;
  st_bbox_y1 += o->y1;
}

uint8_t st_cbbox_x0, st_cbbox_y0, st_cbbox_x1, st_cbbox_y1;
uint8_t st_ClipBBOX(void)
{
  if ( st_bbox_x0 >= 240 )
    return 0;
  if ( st_bbox_x0 >= 0 )
    st_cbbox_x0  = (uint16_t)st_bbox_x0;
  else
    st_cbbox_x0 = 0;

  if ( st_bbox_x1 < 0 )
    return 0;
  if ( st_bbox_x1 < 240 )
    st_cbbox_x1  = (uint16_t)st_bbox_x1;
  else
    st_cbbox_x1 = 240-1;

  if ( st_bbox_y0 >= 320 )
    return 0;
  if ( st_bbox_y0 >= 0 )
    st_cbbox_y0  = (uint16_t)st_bbox_y0;
  else
    st_cbbox_y0 = 0;

  if ( st_bbox_y1 < 0 )
    return 0;
  if ( st_bbox_y1 < 320 )
    st_cbbox_y1  = (uint16_t)st_bbox_y1;
  else
    st_cbbox_y1 = 320-1;
  
  return 1;
}

uint8_t st_IsOut(uint8_t objnr)
{
  st_CalcBBOX(objnr);
  if ( st_bbox_x0 >= 240 )
    return 1;
  if ( st_bbox_x1 < 0 )
    return 1;
  if ( st_bbox_y0 >= 320 )
    return 1;
  if ( st_bbox_y1 < 0 )
    return 1;
  return 0;
}

void st_Disappear(uint8_t objnr)
{
  st_obj *o = st_GetObj(objnr);
  st_player_points += pgm_read_byte(&(st_object_types[o->ot].points));
  o->ot = 0;
}

void st_Move(uint8_t objnr)
{
  st_obj *o = st_GetObj(objnr);
  switch(pgm_read_byte(&(st_object_types[o->ot].move_fn)))
  {
    case ST_MOVE_NONE:
      break;
    case ST_MOVE_X_SLOW:
      o->x -= (1<<ST_FP)/8;
      o->x -= st_difficulty;
      o->y += (int16_t)o->tmp;
      if ( o->y >= ((320-1) << ST_FP) || o->y <= 0 )
        o->tmp = - o->tmp;
      break;
    case ST_MOVE_X_FAST:
      o->x -= (1<<ST_FP)/2;
      o->y += (int16_t)o->tmp;
      if ( o->y >= ((320-1) << ST_FP) || o->y <= 0 )
        o->tmp = - o->tmp;
      break;
    case ST_MOVE_PX_NORMAL:
      o->x += (1<<ST_FP)/4;
      break;
    case ST_MOVE_PX_FAST:
      o->x += (1<<ST_FP);
      break;
    case ST_MOVE_PLAYER:
      o->y = st_player_pos<<ST_FP;
      break;
    case ST_MOVE_PY:
      o->y += 3*ST_FP;
      break;
    case ST_MOVE_NY:
      o->y -= 3*ST_FP;
      break;
    case ST_MOVE_NXPY:
      o->y += 3*ST_FP;
      o->x -= 3*ST_FP;
      break;
    case ST_MOVE_NXNY:
      o->y -= 3*ST_FP;
      o->x -= 3*ST_FP;
      break;
    case ST_MOVE_IMPLODE:
      o->tmp++;
      if ( (o->tmp & 0x03) == 0 )
      {
        if ( o->x0 != o->x1 )
          o->x0++;
        else
            st_Disappear(objnr);
      }
      break;
    case ST_MOVE_WALL:
      o->x -= 1;
      o->x -= (st_difficulty>>1);
      break;
  }
}

void st_DrawBBOX(uint8_t objnr)
{
  st_CalcBBOX(objnr);
  if ( st_ClipBBOX() == 0 )
    return;
  tft.drawRect(st_cbbox_x0, st_cbbox_y0, st_cbbox_x1-st_cbbox_x0+1, st_cbbox_y1-st_cbbox_y0+1, ILI9341_WHITE);
}

void st_DrawBitmap(uint8_t objnr, const uint8_t * bm, uint8_t w, uint8_t h)
{
  st_CalcBBOX(objnr);
  tft.drawBitmap(st_bbox_x0, st_bbox_y1, bm, w, h, ILI9341_WHITE);
}

void st_DrawObj(uint8_t objnr)
{
  st_obj *o = st_GetObj(objnr);
  switch(pgm_read_byte(&(st_object_types[o->ot].draw_fn)))
  {
    case ST_DRAW_NONE:
      break;
    case ST_DRAW_BBOX:
      st_DrawBBOX(objnr);
      break;
    case ST_DRAW_TRASH1:
      st_DrawBitmap(objnr, st_bitmap_trash_5x5_1,o->x1-o->x0+1, 5);
      break;
    case ST_DRAW_TRASH2:
      st_DrawBitmap(objnr, st_bitmap_trash_5x5_2,o->x1-o->x0+1, 5);
      break;
    case ST_DRAW_BIG_TRASH:
      st_DrawBitmap(objnr, st_bitmap_trash_7x7,o->x1-o->x0+1, 7);
      break;
    case ST_DRAW_PLAYER1:
      st_DrawBitmap(objnr, st_bitmap_player1,7,5);
      break;
    case ST_DRAW_PLAYER2:
      st_DrawBitmap(objnr, st_bitmap_player2,7,8);
      break;
    case ST_DRAW_PLAYER3:
      st_DrawBitmap(objnr, st_bitmap_player3,7,11);
      break;
    case ST_DRAW_GADGET:
      st_DrawBitmap(objnr, st_bitmap_gadget,5,5);
      break;
    case ST_DRAW_BACKSLASH:
      {
        uint8_t x;
        uint8_t y;
        x = st_CalcXY(o);
        y = st_px_y;
        tft.drawPixel(x, y, ILI9341_WHITE);  
        x++; y--;
        tft.drawPixel(x, y, ILI9341_WHITE);  
        x++; y--;
        tft.drawPixel(x, y, ILI9341_WHITE);    
      }      
      break;
    case ST_DRAW_SLASH:
      {
        uint8_t x;
        uint8_t y;
        x = st_CalcXY(o);
        y = st_px_y;
        tft.drawPixel(x, y, ILI9341_WHITE);  
        x++; y++;
        tft.drawPixel(x, y, ILI9341_WHITE);  
        x++; y++;
        tft.drawPixel(x, y, ILI9341_WHITE);    
      }      
      break;
  }
}

uint8_t st_IsHitBBOX(uint8_t objnr, uint8_t x, uint8_t y)
{
  st_CalcBBOX(objnr);
  if ( st_ClipBBOX() == 0 ) 
    return 0;
  if ( x < st_cbbox_x0 )
    return 0;
  if ( x > st_cbbox_x1 )
    return 0;
  if ( y < st_cbbox_y0 )
    return 0;
  if ( y > st_cbbox_y1 )
    return 0;
  return 1;
}

void st_Destroy(uint8_t objnr)
{
  int8_t nr;
  st_obj *o = st_GetObj(objnr);
  switch(pgm_read_byte(&(st_object_types[o->ot].destroy_fn)))
  {
    case ST_DESTROY_NONE:
      break;
    case ST_DESTROY_DISAPPEAR:
      st_Disappear(objnr);
      break;
    case ST_DESTROY_GADGET:
      nr = st_FindObj(ST_OT_PLAYER2);
      if ( nr >= 0 )
        st_SetupPlayer(nr, ST_OT_PLAYER3);
      else
      {
        nr = st_FindObj(ST_OT_PLAYER);
        if ( nr >= 0 )
          st_SetupPlayer(nr, ST_OT_PLAYER2);
      }
      st_NewTrashDustAreaArgs(o->x, o->y, ST_OT_DUST_PY);
      st_NewTrashDustAreaArgs(o->x, o->y, ST_OT_DUST_NY);
      o->ot = ST_OT_GADGET_IMPLODE;
      o->tmp = 0;
      break;
    case ST_DESTROY_TO_DUST:
      st_NewTrashDustAreaArgs(o->x, o->y, ST_OT_DUST_PY);
      st_NewTrashDustAreaArgs(o->x, o->y, ST_OT_DUST_NY);
      o->ot = ST_OT_TRASH_IMPLODE;
      o->tmp = 0;
      break;
    case ST_DESTROY_BIG_TRASH:
      st_NewTrashDustAreaArgs(o->x, o->y, ST_OT_DUST_PY);
      st_NewTrashDustAreaArgs(o->x, o->y, ST_OT_DUST_NY);
      st_InitTrash((o->x>>ST_FP)-1, (o->y>>ST_FP)+3, 2+(st_rnd()&3));
      st_InitTrash((o->x>>ST_FP)-2, (o->y>>ST_FP)-3, -2-(st_rnd()&3));
      st_Disappear(objnr);
      break;
    case ST_DESTROY_PLAYER:
      st_Disappear(objnr);
      st_state = ST_STATE_END;
      o->tmp = 0;
      break;
    case ST_DESTROY_PLAYER_GADGETS:
      st_SetupPlayer(objnr, ST_OT_PLAYER);
      break;
  }
}

uint8_t st_IsHit(uint8_t objnr, uint8_t x, uint8_t y, uint8_t missle_mask)
{
  uint8_t hit_mask = st_GetHitMask(objnr);
  st_obj *o;
  
  if ( (hit_mask & missle_mask) == 0 )
    return 0;
  
  o = st_GetObj(objnr);
  
  switch(pgm_read_byte(&(st_object_types[o->ot].is_hit_fn)))
  {
    case ST_IS_HIT_NONE:
      break;
    case ST_IS_HIT_BBOX:
      if ( st_IsHitBBOX(objnr, x, y) != 0 )
      {
        st_Destroy(objnr);
        return 1;
      }
      break;
    case ST_IS_HIT_WALL:
      if ( st_IsHitBBOX(objnr, x, y) != 0 )
      {
        o->x0++;
        if ( o->x0 < o->x1 )
        {
          st_NewTrashDust(x, y, ST_OT_DUST_NXPY);
          st_NewTrashDust(x, y, ST_OT_DUST_NXNY);
        }
        else
        {
          st_Destroy(objnr);
          st_NewTrashDust(x, y, ST_OT_DUST_NXPY);
          st_NewTrashDust(x, y, ST_OT_DUST_NXNY);
          st_NewTrashDust(x, y, ST_OT_DUST_NY);
          st_NewTrashDust(x, y, ST_OT_DUST_PY);
        }
        return 1;
      }
      break;
  }
  return 0;
}

uint8_t st_fire_player = 0;
uint8_t st_fire_period = 51;
uint8_t st_manual_fire_delay = 20;
uint8_t st_is_fire_last_value = 0;

void st_FireStep(uint8_t is_auto_fire, uint8_t is_fire)
{
  if ( is_auto_fire != 0 )
  {
    st_fire_player++;
    if ( st_fire_player >= st_fire_period )
      st_fire_player = 0;
  }
  else
  {
    if ( st_fire_player < st_manual_fire_delay )
    {
      st_fire_player++;
    }
    else
    {
      if ( st_is_fire_last_value == 0 )
        if ( is_fire != 0 )
          st_fire_player = 0;
    }
    st_is_fire_last_value = is_fire;
  }
}

void st_Fire(uint8_t objnr)
{
  st_obj *o = st_GetObj(objnr);
  uint8_t x;
  uint8_t y;
  
  switch(pgm_read_byte(&(st_object_types[o->ot].fire_fn)))
  {
    case ST_FIRE_NONE: 
      break;
    case ST_FIRE_PLAYER1: 
      if ( st_fire_player == 0 )
      {
        x = st_CalcXY(o);
        y = st_px_y;
        st_NewPlayerMissle(x , y );
      }
      break;
    case ST_FIRE_PLAYER2:
      if ( st_fire_player == 0 )
      {
        x = st_CalcXY(o);
        y = st_px_y;
        st_NewPlayerMissle(x , y );
        st_NewPlayerMissle(x , y+4 );
      }
      break;
    case ST_FIRE_PLAYER3:
      if ( st_fire_player == 0 )
      {
        x = st_CalcXY(o);
        y = st_px_y;
        st_NewPlayerMissle(x , y );
        st_NewPlayerMissle(x , y+4 );
        st_NewPlayerMissle(x , y-4 );
      }
      break;
  }
}

void st_NewGadget(uint8_t x, uint8_t y)
{
  st_obj *o;
  int8_t objnr = st_NewObj();
  if ( objnr < 0 )
    return;
  o = st_GetObj(objnr);
  st_SetXY(o, x, y);
  o->ot = ST_OT_GADGET;
  o->tmp = 8;
  o->x0 = -3;
  o->x1 = 1;
  o->y0 = -2;
  o->y1 = 2;
}

void st_InitTrash(uint8_t x, uint8_t y, int8_t dir)
{
  st_obj *o;
  int8_t objnr = st_NewObj();
  if ( objnr < 0 )
    return;
  o = st_GetObj(objnr);
  if ( (st_rnd() & 1) == 0 )
    o->ot = ST_OT_TRASH1;
  else
    o->ot = ST_OT_TRASH2;
  if ( dir == 0 )
  {
    o->tmp = 0;
    if ( st_rnd() & 1 )
    {
      if ( st_rnd() & 1 )
        o->tmp++;
      else
        o->tmp--;
    }
  }
  else
  {
    o->tmp = dir;
  }
  st_SetXY(o, x, y);
  o->x0 = -3;
  o->x1 = 1;
  o->y0 = -2;
  o->y1 = 2;
  if ( st_difficulty >= 5 )
  {
    if ( (st_rnd() & 3) == 0 )
    {
      o->ot = ST_OT_BIG_TRASH;
      o->y0--;
      o->y1++;
      o->x0--;
      o->x1++;
    }
  }
}

void st_NewTrashDust(uint8_t x, uint8_t y, int ot)
{
  st_obj *o;
  int8_t objnr = st_NewObj();
  if ( objnr < 0 )
    return;
  o = st_GetObj(objnr);
  o->ot = ot;
  st_SetXY(o, x, y);
  o->x0 = 0;
  o->x1 = 0;
  o->y0 = -2;
  o->y1 = 2;
}

void st_NewTrashDustAreaArgs(int16_t x, int16_t y, int ot)
{
  st_NewTrashDust(x>>ST_FP, y>>ST_FP, ot);
}

void st_NewWall(void)
{
  st_obj *o;
  int8_t objnr = st_NewObj();
  int8_t h;
  if ( objnr < 0 )
    return;
  o = st_GetObj(objnr);
  o->ot = ST_OT_WALL_SOLID;
  h = st_rnd();
  h &= 63;
  h = (int8_t)(((int16_t)h*(int16_t)(320/4))>>6);
  h += 320/6;

  o->x0 = 0;
  o->x1 = 5;
  o->x = (240-1)<<ST_FP;
 
  if ( (st_rnd() & 1) == 0 )
  {
    o->y = (320-1)<<ST_FP;
    
    o->y0 = -h;
    o->y1 = 0;
  }
  else
  {
    o->y = (0)<<ST_FP;
    o->y0 = 0;
    o->y1 = h;
  }
}

void st_NewPlayerMissle(uint8_t x, uint8_t y)
{
  st_obj *o;
  int8_t objnr = st_NewObj();
  if ( objnr < 0 )
    return;
  o = st_GetObj(objnr);
  o->ot = ST_OT_MISSLE;
  st_SetXY(o, x, y);
  o->x0 = -4;
  o->x1 = 1;
  o->y0 = 0;
  o->y1 = 0;
}

void st_SetupPlayer(uint8_t objnr, uint8_t ot)
{
  st_obj *o = st_GetObj(objnr);
  switch(ot)
  {
    case ST_OT_PLAYER:
      o->ot = ot;
      o->y0 = -2;
      o->y1 = 2;
      break;
    case ST_OT_PLAYER2:
      o->ot = ot;
      o->y0 = -2;
      o->y1 = 5;
      break;
    case ST_OT_PLAYER3:
      o->ot = ot;
      o->y0 = -5;
      o->y1 = 5;
      break;
  }
}

void st_NewPlayer(void)
{
  st_obj *o;
  int8_t objnr = st_NewObj();
  if ( objnr < 0 )
    return;
  o = st_GetObj(objnr);
  o->x = 6<<ST_FP;
  o->y = (320/2)<<ST_FP;
  o->x0 = -6;
  o->x1 = 0;
  st_SetupPlayer(objnr, ST_OT_PLAYER);
}

void st_InitDeltaWall(void)
{
  uint8_t i;
  uint8_t cnt = 0;
  uint8_t max_x = 0;
  uint8_t max_l;
  
  uint8_t min_dist_for_new = 40;
  uint8_t my_difficulty = st_difficulty;
  
  if ( st_difficulty >= 2 )
  {
    
    max_l = 240;
    max_l -= min_dist_for_new;
    
    if ( my_difficulty > 30 )
      my_difficulty = 30;
    min_dist_for_new -= my_difficulty;
    
    for( i = 0; i < 60; i++ )
    {
      if ( st_objects[i].ot == ST_OT_WALL_SOLID )
      {
        cnt++;
        if ( max_x < (st_objects[i].x>>ST_FP) )
          max_x = (st_objects[i].x>>ST_FP);
      }
    }    
    if ( max_x < max_l ) 
    {
      st_NewWall();
    }
  }
}

void st_InitDeltaTrash(void)
{
  uint8_t i;
  uint8_t cnt = 0;
  uint8_t max_x = 0;
  uint8_t max_l;
  
  uint8_t upper_trash_limit = 60-7;
  uint8_t min_dist_for_new = 20;
  uint8_t my_difficulty = st_difficulty;
  
  if ( my_difficulty > 14 )
    my_difficulty = 14;
  min_dist_for_new -= my_difficulty;
  
  for( i = 0; i < 60; i++ )
  {
    if ( st_objects[i].ot == ST_OT_TRASH1 || st_objects[i].ot == ST_OT_TRASH2 || st_objects[i].ot == ST_OT_GADGET  || st_objects[i].ot == ST_OT_BIG_TRASH )
    {
      cnt++;
      if ( max_x < (st_objects[i].x>>ST_FP) )
        max_x = (st_objects[i].x>>ST_FP);
    }
  }
  
  max_l = 240;
  max_l -= min_dist_for_new;
  
  if ( cnt < upper_trash_limit )
    if ( max_x < max_l ) 
    {
      if (  (st_difficulty >= 3)  && ((st_rnd() & 7) == 0) )
        st_NewGadget(240-1, rand() & (320-1));
      else
        st_InitTrash(240-1, rand() & (320-1),0 );
    }
}

void st_InitDelta(void)
{
  st_InitDeltaTrash();
  st_InitDeltaWall();
}

void st_DrawInGame(uint8_t fps)
{
  uint8_t i;
  for( i = 0; i < 60; i++ )
      st_DrawObj(i);

  tft.fillRect(0, 0, 240, 8, ILI9341_BLACK);
  tft.drawFastHLine(0, 8, 240, ILI9341_WHITE);
  tft.setCursor(0, 0);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(1);
  tft.print(st_itoa(st_difficulty));
  tft.drawFastHLine(10, 10, (st_to_diff_cnt>>ST_DIFF_FP)+1, ILI9341_WHITE);
  tft.drawFastVLine(10, 7, 3, ILI9341_WHITE);
  tft.drawFastVLine(10+ST_DIFF_VIS_LEN, 7, 3, ILI9341_WHITE);
  
  tft.setCursor(180, 0);
  tft.print(st_itoa(st_player_points_delayed));
  
  if ( fps > 0 )
  {
    tft.setCursor(90, 0);
    tft.print("FPS:");
    tft.print(st_itoa(fps));
  }
}

void st_Draw(uint8_t fps)
{
  switch(st_state)
  { 
    
    case ST_STATE_PREPARE:
    case ST_STATE_IPREPARE:
      tft.setCursor(31, 160);
      tft.setTextColor(ILI9341_WHITE);
      tft.setTextSize(2);
      tft.print("Space Trash");
      tft.setCursor(18, 145);
      tft.print("SPACE TRASH!!!");
      tft.drawFastHLine(240-st_to_diff_cnt-10, 160, 11, ILI9341_WHITE);
      break;
    case ST_STATE_GAME:
      st_DrawInGame(fps);
      break;
    case ST_STATE_END:
    case ST_STATE_IEND:
      tft.setCursor(0, 160);
      tft.setTextColor(ILI9341_WHITE);
      tft.setTextSize(1);
      tft.print("Game Over");
      tft.setCursor(50, 160);
      tft.print(st_itoa(st_player_points));
      tft.setCursor(75, 160);
      tft.print(st_itoa(st_highscore));
      tft.drawFastHLine(st_to_diff_cnt, 160, 11, ILI9341_WHITE);
      break;
  }
}

void st_SetupInGame(void)
{ 
  st_player_points = 0;
  st_player_points_delayed = 0;
  st_difficulty = 1;
  st_to_diff_cnt = 0;
  st_ClrObjs();
  st_NewPlayer();
}

void st_Setup(void)
{ 
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);
}

void st_StepInGame(uint8_t player_pos, uint8_t is_auto_fire, uint8_t is_fire)
{
  uint8_t i, j;
  uint8_t missle_mask;
  
  if ( player_pos < 64 )
    st_player_pos = 0;
  else if ( player_pos >= 192 )
    st_player_pos = 320-2-1;
  else 
    st_player_pos = ((uint16_t)((player_pos-64)) * (uint16_t)(320-2))/128;
  st_player_pos+=1;
  for( i = 0; i < 60; i++ )
    st_Move(i);

  for( i = 0; i < 60; i++ )
    if ( st_objects[i].ot != 0 )
      if ( st_IsOut(i) != 0 )
        st_Disappear(i);
      
  for( i = 0; i < 60; i++ )
  {
    missle_mask = st_GetMissleMask(i);
    if ( missle_mask != 0 )
      if ( st_CalcXY(st_objects+i) != 0 )
        for( j = 0; j < 60; j++ )
          if ( i != j )
            if ( st_IsHit(j, st_px_x, st_px_y, missle_mask) != 0 )
            {
              st_Destroy(i);
            }
  }
  
  st_FireStep(is_auto_fire, is_fire);
  
  for( i = 0; i < 60; i++ )
    st_Fire(i);
    
  st_InitDelta();
  
  st_to_diff_cnt++;
  if ( st_to_diff_cnt == (ST_DIFF_VIS_LEN<<ST_DIFF_FP) )
  {
    st_to_diff_cnt = 0;
    st_difficulty++;
    st_player_points += ST_POINTS_PER_LEVEL;
  }  
  
  if ( st_player_points_delayed < st_player_points )
    st_player_points_delayed++;
}
  
void st_Step(uint8_t player_pos, uint8_t is_auto_fire, uint8_t is_fire)
{
  switch(st_state)
  {
    case ST_STATE_PREPARE:
      st_to_diff_cnt = 240-10;
      st_state = ST_STATE_IPREPARE;
      break;
    case ST_STATE_IPREPARE:
      st_to_diff_cnt--;
      if ( st_to_diff_cnt == 0 )
      {
        st_state = ST_STATE_GAME;
        st_SetupInGame();
      }
      break;
    case ST_STATE_GAME:
      st_StepInGame(player_pos, is_auto_fire, is_fire);
      break;
    case ST_STATE_END:
      st_to_diff_cnt = 240-10;
      if ( st_highscore < st_player_points)
        st_highscore = st_player_points;
      st_state = ST_STATE_IEND;
      break;
    case ST_STATE_IEND:
      st_to_diff_cnt--;
      if ( st_to_diff_cnt == 0 )
        st_state = ST_STATE_PREPARE;
      break;
  }
}

void setup(void) {
  st_Setup();
}

uint8_t a;
uint8_t b;
uint8_t y = 128;

void loop(void) {
  for(;;)
  {
    st_Step(y, 0, digitalRead(pin_fire));
    tft.fillScreen(ILI9341_BLACK);
    st_Draw(0);
    
    if ( digitalRead(pin_down) ) {
      y++;
    }
    
    if ( digitalRead(pin_up) ) {
      y--;
    }
  }
}