
#define DEBUG_ENABLE 0
#define FPS 30
#define COLOR_BG 50 50 50 255
#define COLOR_FG 222 222 222 255
#define PAD_SIZE 60
#define PAD_DISTANCE 20
#define BALL_SPEED 5.0
#define BALL_SPEEDUP 0.1
#define BALL_SIZE 10

#if DEBUG_ENABLE
#define DBG(...) { \
   new _buf[128]; \
   snprintf(_buf, sizeof(_buf), __VA_ARGS__); \
   set_text(PART:"dbg", _buf); }
#else
#define DBG(...)
#endif

collections {
   group { name: "main";
      min: 500 300;
      script {
         public ballx, bally;
         public ballspeedx, ballspeedy;
         public player_score, ai_score;

         public game_reset() {
            set_int(ai_score, 0);
            set_int(player_score, 0);
            set_text(PART:"score1", "0");
            set_text(PART:"score2", "0");
            game_init();
         }
         
         public game_init() {
            set_float(ballspeedx, 0.0);
            set_float(ballspeedy, 0.0);
            set_float(ballx, 100);
            set_float(bally, 100);
         }
         
         public start_game() {
            set_float(ballspeedx, BALL_SPEED);
            set_float(ballspeedy, BALL_SPEED * randf());
         }

         public player_wins() {
            new buf[16]
            set_int(player_score, get_int(player_score) + 1);
            snprintf(buf, sizeof(buf), "%d", get_int(player_score));
            set_text(PART:"score1", buf);
            game_init();
         }

         public ai_wins() {
            new buf[16]
            set_int(ai_score, get_int(ai_score) + 1);
            snprintf(buf, sizeof(buf), "%d", get_int(ai_score));
            set_text(PART:"score2", buf);
            game_init();
         }

         public game_loop(count) {
            count++;

            /* get field geometry */
            new fx, fy, fw, fh;
            get_geometry(PART:"bg", fx, fy, fw, fh);

            /* get mouse coords */
            new mx, my;
            get_mouse(mx, my);

            /* get the ball info */
            new Float:bx = get_float(ballx);
            new Float:by = get_float(bally);
            new Float:speedx = get_float(ballspeedx);
            new Float:speedy = get_float(ballspeedy);

            /* move the player pad */
            new pady = my - PAD_SIZE / 2;
            if (pady < 0) pady = 0;
            else if (pady + PAD_SIZE > fh) pady = fh - PAD_SIZE;
            custom_state(PART:"pad1", "default", 0.0);
            set_state_val(PART:"pad1", STATE_REL1_OFFSET, 20, pady);
            set_state(PART:"pad1", "custom", 0.0);

            /* move the AI pad */
            new pad2y = round(by) - PAD_SIZE / 2 - BALL_SIZE / 2;
            if (pad2y < 0) pad2y = 0;
            else if (pad2y + PAD_SIZE > fh) pad2y = fh - PAD_SIZE;
            custom_state(PART:"pad2", "default", 0.0);
            set_state_val(PART:"pad2", STATE_REL1_OFFSET, 20, pad2y);
            set_state(PART:"pad2", "custom", 0.0);
         
            /* calc new ball position */
            bx += speedx;
            by += speedy;

            /* check walls collision */
            if (by < 0)
            {
               speedy = -speedy;
               by = 0;
            }
            else if (by + BALL_SIZE > fh)
            {
               speedy = -speedy;
               by = fh - BALL_SIZE - 1;
            }

            /* check player pad collision */
            if ((speedx < 0) &&
                (bx < PAD_DISTANCE + 10) && (bx > 0) &&
                (by + BALL_SIZE > pady) && (by < pady + PAD_SIZE))
            {
               new Float:dy = by - pady - PAD_SIZE / 2;
               speedy += dy / 10;
               speedx = -speedx + BALL_SPEEDUP;
            }

            /* check AI pad collision */
            else if ((bx + BALL_SIZE > fw - PAD_DISTANCE - 10) &&
                     (bx + BALL_SIZE < fw) &&
                     (by + BALL_SIZE > pad2y) && (by < pad2y + PAD_SIZE))
            {
               new Float:dy = by - pad2y - PAD_SIZE / 2;
               speedy += dy / 10;
               speedx = -speedx - BALL_SPEEDUP;
            }
            
            /* apply the new ball position */
            custom_state(PART:"ball", "default", 0.0);
            set_state_val(PART:"ball", STATE_REL1_OFFSET, round(bx), round(by));
            set_state(PART:"ball", "custom", 0.0);

            /* update global vars */
            set_float(ballx, bx);
            set_float(bally, by);
            set_float(ballspeedx, speedx);
            set_float(ballspeedy, speedy);

            /* AI score a point */
            if (bx < 0) ai_wins();
            /* player score a point */
            if (bx + BALL_SIZE > fw) player_wins();

            /* show debug info (if debug enabled) */
            DBG("loop:%d  [speed %f  %f] [mouse: %d %d] [ball: %f %f]",
                count, speedx, speedy, mx, my, bx, by)

            /* recall the loop in n seconds */
            timer(1.0 / FPS, "game_loop", count);
         }
      }
      parts {
         part { name: "bg";
            type: RECT;
            description { state: "default" 0.0;
               color: COLOR_BG;
            }
         }
         part { name: "net";
            type: RECT;
            description { state: "default" 0.0;
               color: COLOR_FG;
               max: 10 9999;
            }
         }
         part { name: "score1";
            type: TEXT;
            description { state: "default" 0.0;
               color: COLOR_FG;
               rel2.relative: 0.5 0.5;
               rel2.offset: -20 0;
               text {
                  text: "0";
                  font: "Sans";
                  size: 50;
                  align: 1.0 0.0;
               } 
            }
         }
         part { name: "score2";
            type: TEXT;
            description { state: "default" 0.0;
               color: COLOR_FG;
               rel1.relative: 0.5 0.0;
               rel1.offset: 20 0;
               text {
                  text: "0";
                  font: "Sans";
                  size: 50;
                  align: 0.0 0.0;
               } 
            }
         }
         part { name: "pad1";
            type: RECT;
            description { state: "default" 0.0;
               color: COLOR_FG;
               max: 10 PAD_SIZE;
               fixed: 1 1;
               align: 0.0 0.0;
               rel1.offset: PAD_DISTANCE 0;
            }
         }
         part { name: "pad2";
            type: RECT;
            description { state: "default" 0.0;
               color: COLOR_FG;
               max: 10 PAD_SIZE;
               fixed: 1 1;
               align: 1.0 0.0;
               rel2.offset: -PAD_DISTANCE 0;
            }
         }
         part { name: "ball";
            type: RECT;
            description { state: "default" 0.0;
               color: COLOR_FG;
               max: BALL_SIZE BALL_SIZE;
               fixed: 1 1;
               align: 0.0 0.0;
               rel1.offset: 100 100;
            }
         }
         #if DEBUG_ENABLE
         part { name: "dbg";
            type: TEXT;
            description { state: "default" 0.0;
               color: 255 255 255 200;
               text {
                  font: "Sans";
                  size: 12;
                  align: 1.0 1.0;
               }
            }
         }
         #endif
      }
      programs {
         /* on load: reset the game and start the game loop */
         program {
            signal: "load";
            source: "";
            script {
               game_reset();
               timer(0.1 , "game_loop", 0);
            }
         }
         /* mouse left click: start the game, if not yet started */
         program {
            signal: "mouse,down,1";
            source: "bg";
            script {
               if (get_float(ballspeedx) == 0.0)
                  start_game();
            }
         }
         /* mouse right click: restart the game*/
         program {
            signal: "mouse,down,3";
            source: "bg";
            script {
               game_reset();
            }
         }
      }
   }
}
