#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);

#define pin_up A5
#define pin_down A3

uint8_t y = 128;

/* 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_FP 4

#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_RED);
      tft.setTextSize(2);
      tft.print("GAMESHINE");
      tft.setTextColor(ILI9341_WHITE);
      tft.setTextSize(2);
      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 drawHexagon(int x, int y, int size, uint16_t color) {
  for (int i = 0; i < 6; i++) {
    int x0 = x + size * cos(i * PI / 3);
    int y0 = y + size * sin(i * PI / 3);
    int x1 = x + size * cos((i + 1) * PI / 3);
    int y1 = y + size * sin((i + 1) * PI / 3);
    tft.drawLine(x0, y0, x1, y1, color);
  }
}

void drawStaticBackground() {
  tft.fillScreen(ILI9341_BLACK);
  
  // Draw some static shapes
  for (int i = 0; i < 20; i++) {
    int x = rand() % 240;
    int y = rand() % 320;
    int r = rand() % 20 + 5;
    tft.drawCircle(x, y, r, ILI9341_BLUE);
  }

  for (int i = 0; i < 20; i++) {
    int x = rand() % 240;
    int y = rand() % 320;
    int w = rand() % 40 + 10;
    int h = rand() % 40 + 10;
    tft.drawRect(x, y, w, h, ILI9341_GREEN);
  }

  for (int i = 0; i < 20; i++) {
    int x0 = rand() % 240;
    int y0 = rand() % 320;
    int x1 = rand() % 240;
    int y1 = rand() % 320;
    tft.drawLine(x0, y0, x1, y1, ILI9341_RED);
  }
}

void splashScreen() {
  tft.fillScreen(ILI9341_BLACK);

  // Draw "GAMESHINE" logo with pulsing animation
  static float textSize = 3.0;
  static bool increasing = true;
  const float textSizeMin = 2.5;
  const float textSizeMax = 3.5;
  const float textSizeStep = 0.05;

  if (increasing) {
    textSize += textSizeStep;
    if (textSize > textSizeMax) increasing = false;
  } else {
    textSize -= textSizeStep;
    if (textSize < textSizeMin) increasing = true;
  }

  const char *title = "GAMESHINE";
  const char *subtitle = "A Fab Academy Project";
  
  // Center text calculations
  int16_t x1, y1;
  uint16_t w, h;

  // Calculate center for title
  tft.setTextSize(textSize);
  tft.getTextBounds(title, 0, 0, &x1, &y1, &w, &h);
  int16_t x = (310 - w) / 2 + 10;
  int16_t y = (320 - h) / 2 - 60 ;
  
  tft.setCursor(x, y);
  tft.setTextColor(ILI9341_RED);
  tft.println(title);

  // Calculate center for subtitle
  tft.setTextSize(2);
  tft.getTextBounds(subtitle, 0, 0, &x1, &y1, &w, &h);
  x = (310 - w) / 2 + 10;
  y = (320 - h) / 2 - 35;
  
  tft.setCursor(x, y);
  tft.setTextColor(ILI9341_WHITE);
  tft.println(subtitle);
}
void menu() {
  tft.fillScreen(ILI9341_BLACK);

    // Draw hexagons
  for (int i = 0; i < 50; i++) {
    int x = rand() % 240;
    int y = rand() % 320;
    int size = rand() % 20 + 10;
    uint16_t color = rand() % 65536;
    drawHexagon(x, y, size, color);
  }

  // Animate the "GAMESHINE" logo
  static uint8_t colorIndex = 0;
  uint16_t colors[] =  { ILI9341_RED, ILI9341_GREEN, ILI9341_BLUE, ILI9341_YELLOW, ILI9341_CYAN };
  tft.setCursor(30, 50);
  tft.setTextColor(colors[colorIndex]);
  tft.setTextSize(3);
  tft.println("GAMESHINE");

  tft.setCursor(50, 150);
  tft.setTextColor(ILI9341_GREEN);
  tft.setTextSize(2);
  tft.println("1. Space Trash");
  tft.setCursor(50, 200);
  tft.setTextColor(ILI9341_BLUE);
  tft.println("2. GalaFab");

  // Update color index
  colorIndex = (colorIndex + 1) % 5;
}


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

unsigned long startTime;
bool splashScreenDone = false;
uint8_t gameSelection = 0;

void loop() {
  if (!splashScreenDone) {
    unsigned long currentTime = millis();
    if (currentTime - startTime > 15000) { // Show splash screen for 15 seconds
      splashScreenDone = true;
    } else {
      splashScreen();
    }
  } else {
    static unsigned long lastUpdate = 0;
    if (millis() - lastUpdate > 500) { // Update every 500 milliseconds
      menu();
      lastUpdate = millis();
    }

    if (gameSelection == 0) {
      if (digitalRead(pin_up)) {
        gameSelection = 1;
        delay(200);
      } else if (digitalRead(pin_down)) {
        gameSelection = 2;
        delay(200);
      }
    }

    if (gameSelection == 1) {
      st_Setup();
      for (;;) {
        static uint8_t direction = 0; // 0 for down, 1 for up
        st_Step(y, 1, 1); // Automatic fire enabled

        if (direction == 0) {
          y++;
          if (y >= 255) {
            direction = 1;
          }
        } else {
          y--;
          if (y <= 1) {
            direction = 0;
          }
        }

        tft.fillScreen(ILI9341_BLACK);
        st_Draw(0);
      }
    } else if (gameSelection == 2) {

      // To be implemented...
    }
  }
}